将对象数组转换为单个对象并在 TypeScript 中保留类型
Transform array of objects to single object and keep types in TypeScript
我有一个包含键 name
和 value
的对象数组。我想将此数组转换为单个对象,其中键是 name
,值是输入对象的 value
属性。
type Input = { name: string, value: any }[]
type Output = Record<string, any> // Key-value object { [name]: value }
const input: Input = [
{ name: 'name', value: 'Michal' },
{ name: 'age', value: 24 },
{ name: 'numbers', value: [4, 7, 9] }
]
const getOutput = (input: Input): Output => {
return input.reduce((output, record) => ({ ...output, [record.name]: record.value }), {})
}
// Output is: { name: 'Michal', age: 24, numbers: [4, 7, 9] }
const output: Output = getOutput(input)
上面的例子是有效的,但是我使用 Record<string, any>
类型作为输出。这意味着我失去了价值观的类型。有什么方法可以执行此转换,但保留类型?
output.age.length // Should be TS error, `number` has no `length` property
output.numbers.length // 3
output.address // Should be TS error, `input` has no `address` property
您的输入数据结构似乎令人惊讶,也许您应该考虑改变处理数据的方式
您可以在保持输入值不变的情况下对问题进行排序的一种方法:
type ValueDescriptor = {type:'NUMBER',value:number} | {type:'STRING',value:string} | {type:'NUMBER_ARRAY',value:number[]} /* | ... any business-backed type*/
type Output = Record<string, ValueDescriptor>
type Elem<V> = { name: string, value: V }
type Callback<Item> =
Item extends { name: infer Name, value: infer Value }
? Name extends PropertyKey
? Record<Name, Value> : never : never
type Reducer<T extends Array<any>, Acc = {}> =
T extends []
? Acc
: T extends [infer Head, ...infer Tail]
? Reducer<Tail, Acc & Callback<Head>>
: never
const getOutput = <
N extends number,
Value extends number | string | [N, ...N[]],
Name extends string,
Item extends { name: Name, value: Value },
Input extends Item[]
>(input: [...Input]) =>
input.reduce((output, record) =>
({ ...output, [record.name]: record.value }),
{} as Reducer<Input>
)
const output = getOutput([
{ name: 'name', value: 'Michal' },
{ name: 'age', value: 24 },
{ name: 'numbers', value: [4, 7, 9] }
])
output.age // 24
output.name // 'MIchal'
output.numbers // [4,7,9]
说明
Reducer
和 Callback
- 工作方式几乎与 Array.prototype.reducer
完全相同,除了它迭代递归地。
这是 Reducer 的 js 表示:
const Callback = (elem) => {
const { name, value } = elem;
return { [name]: value }
}
const reducer = (arr: ReadonlyArray<any>, result: Record<string, any> = {}): Record<string, any> => {
if (arr.length === 0) {
return result
}
const [head, ...tail] = arr;
return reducer(tail, { ...result, ...Callback(head) }
}
有关详细信息,请参阅 answer and my blog。
[...Input]
- 我已经使用 variadic tuple types 来推断数组中的每个对象
我有一个包含键 name
和 value
的对象数组。我想将此数组转换为单个对象,其中键是 name
,值是输入对象的 value
属性。
type Input = { name: string, value: any }[]
type Output = Record<string, any> // Key-value object { [name]: value }
const input: Input = [
{ name: 'name', value: 'Michal' },
{ name: 'age', value: 24 },
{ name: 'numbers', value: [4, 7, 9] }
]
const getOutput = (input: Input): Output => {
return input.reduce((output, record) => ({ ...output, [record.name]: record.value }), {})
}
// Output is: { name: 'Michal', age: 24, numbers: [4, 7, 9] }
const output: Output = getOutput(input)
上面的例子是有效的,但是我使用 Record<string, any>
类型作为输出。这意味着我失去了价值观的类型。有什么方法可以执行此转换,但保留类型?
output.age.length // Should be TS error, `number` has no `length` property
output.numbers.length // 3
output.address // Should be TS error, `input` has no `address` property
您的输入数据结构似乎令人惊讶,也许您应该考虑改变处理数据的方式
您可以在保持输入值不变的情况下对问题进行排序的一种方法:
type ValueDescriptor = {type:'NUMBER',value:number} | {type:'STRING',value:string} | {type:'NUMBER_ARRAY',value:number[]} /* | ... any business-backed type*/
type Output = Record<string, ValueDescriptor>
type Elem<V> = { name: string, value: V }
type Callback<Item> =
Item extends { name: infer Name, value: infer Value }
? Name extends PropertyKey
? Record<Name, Value> : never : never
type Reducer<T extends Array<any>, Acc = {}> =
T extends []
? Acc
: T extends [infer Head, ...infer Tail]
? Reducer<Tail, Acc & Callback<Head>>
: never
const getOutput = <
N extends number,
Value extends number | string | [N, ...N[]],
Name extends string,
Item extends { name: Name, value: Value },
Input extends Item[]
>(input: [...Input]) =>
input.reduce((output, record) =>
({ ...output, [record.name]: record.value }),
{} as Reducer<Input>
)
const output = getOutput([
{ name: 'name', value: 'Michal' },
{ name: 'age', value: 24 },
{ name: 'numbers', value: [4, 7, 9] }
])
output.age // 24
output.name // 'MIchal'
output.numbers // [4,7,9]
说明
Reducer
和 Callback
- 工作方式几乎与 Array.prototype.reducer
完全相同,除了它迭代递归地。
这是 Reducer 的 js 表示:
const Callback = (elem) => {
const { name, value } = elem;
return { [name]: value }
}
const reducer = (arr: ReadonlyArray<any>, result: Record<string, any> = {}): Record<string, any> => {
if (arr.length === 0) {
return result
}
const [head, ...tail] = arr;
return reducer(tail, { ...result, ...Callback(head) }
}
有关详细信息,请参阅
[...Input]
- 我已经使用 variadic tuple types 来推断数组中的每个对象