【问题标题】:Transform array of objects to single object and keep types in TypeScript将对象数组转换为单个对象并在 TypeScript 中保留类型
【发布时间】:2021-11-16 19:51:57
【问题描述】:

我有一个带有键 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&lt;string, any&gt; 类型作为输出。这意味着我失去了价值观的类型。有没有办法执行这种转换,但保留类型?

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

【问题讨论】:

  • this code 是否满足您的需求?如果是这样,我很乐意写一个答案;如果没有,请edit 包含不满意的用例的问题。

标签: typescript typescript-generics


【解决方案1】:

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]

Playground

说明

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) }
}

请参阅this 答案和my blog 了解更多信息。

[...Input] - 我已经使用variadic tuple types 来推断数组中的每个对象

【讨论】:

    【解决方案2】:

    您的输入数据结构似乎令人惊讶,也许您应该考虑改变处理数据的方式

    您可以在保持输入值不变的情况下对问题进行排序:

    type ValueDescriptor = {type:'NUMBER',value:number} | {type:'STRING',value:string} | {type:'NUMBER_ARRAY',value:number[]} /* | ... any business-backed type*/
    
    type Output = Record<string, ValueDescriptor>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-26
      • 2020-05-03
      • 2021-11-04
      • 1970-01-01
      • 2020-12-28
      • 1970-01-01
      • 1970-01-01
      • 2019-02-12
      相关资源
      最近更新 更多