Home > Net >  How to make make keys of objects to work dynamically in typescript?
How to make make keys of objects to work dynamically in typescript?

Time:12-09

I have array of object data and I want to dynamically convert them to object with key and array. How to make make keys of objects to work dynamically in typescript?

For example if the data be like:

data1 = [
  {a: 'st1', b: 1, c: 1, d: 1, e: 'e1' },
  {a: 'st2', b: 2, c: 2, d: 2, e: 'e2'},
  {a: 'st3', b: 3, c: 3, d: 3, e: 'e3'},
  {a: 'st4', b: 4, c: 4, d: 4, e: 'e4' },
]    

and I it to convert to:

data2= {
   a: ['st1', 'st2', 'st3', 'st4',]
   b: [1, 2, 3, 4,],
   c: [1, 2, 3, 4,],
   d: [1, 2, 3, 4,],
   e: ['e1', 'e2', 'e3', 'e4']
}

The simplest solution is to

type Data2Props = {
  a: string[],
  b: number[],
  c: number[],
  d: number[],
  e: string[]
}

const data2: Data2Props = {
  a: [],
  b: [],
  c: [],
  d: [],
  e: []
}

data1?.forEach((item) => {
   data2.a.push(item.a)
   data2.b.push(item.b)
   data2.c.push(item.c)
   data2.d.push(item.d)
   data2.e.push(item.e)
})

But what if the number of keys increase, is there any better and more concise solution?

Something like this will work in javascript but not in typescript.

let keys: string[] = ['a', 'b', 'c', 'd', 'e'];

let data2 = {}
keys.forEach((key) => data2[key] = [])
data1?.forEach((item) => {
 keys.forEach((key) => data2[key].push(item[key])
}

In typescript it will return an error of typing.

CodePudding user response:

You can do something like below in typescript

let data1:{[key:string]:any}[] = [
  {a: 'st1', b: 1, c: 1, d: 1, e: 'e1' },
  {a: 'st2', b: 2, c: 2, d: 2, e: 'e2'},
  {a: 'st3', b: 3, c: 3, d: 3, e: 'e3'},
  {a: 'st4', b: 4, c: 4, d: 4, e: 'e4' },
];

let keys: string[] = ['a', 'b', 'c', 'd', 'e'];

let data2:{[key:string]:any[]} = {}
keys.forEach((key) => data2[key] = [])
data1?.forEach((item) => {
 keys.forEach((key) => data2[key].push(item[key]))
});

console.log(data2);

Playground link

Or even without defining keys array

let data1:{[key:string]:string|number}[] = [
      {a: 'st1', b: 1, c: 1, d: 1, e: 'e1' },
      {a: 'st2', b: 2, c: 2, d: 2, e: 'e2'},
      {a: 'st3', b: 3, c: 3, d: 3, e: 'e3'},
      {a: 'st4', b: 4, c: 4, d: 4, e: 'e4' },
    ]  ;

  let data2:{[key:string]:any[]}={};
    data1.forEach(obj=>{
    Object.keys(obj).forEach(key=>{
    let val=obj[key];
     if(data2[key]){
     data2[key].push(val);
     }else{
     data2[key]=[val];
     }
    })
  });

  console.log(data2)

Playground link

CodePudding user response:

The approach below requires a bunch of type assertions inside the transpose function, but usage of the function is entirely type-safe:

const ITEM = {
    a: '',
    b: 0,
    c: 0,
    d: 0,
    e: '',
}

type Item = typeof ITEM

type TransposedItems = {
    [Key in keyof Item]: Item[Key][]
}

function transpose(items: Item[]): TransposedItems {
    const transposed: Partial<TransposedItems> = {}
    for (const key in ITEM) {
        transposed[key as keyof Item] = items.map(item => item[key as keyof Item]) as any[]
    }
    return transposed as TransposedItems
}

const data1: Item[] = [
  {a: 'st1', b: 1, c: 1, d: 1, e: 'e1' },
  {a: 'st2', b: 2, c: 2, d: 2, e: 'e2'},
  {a: 'st3', b: 3, c: 3, d: 3, e: 'e3'},
  {a: 'st4', b: 4, c: 4, d: 4, e: 'e4' },
]
console.log(transpose(data1))

The trick is to define a constant ITEM with the same type as the actual elements, and let type inference do its work on it. Because ITEM exists at runtime, we can iterate over its keys, something that is difficult to do if it were only a type that existed at compile time.

You could also just use the first item in the array for this, but you'll run into trouble if the objects aren't all exactly the same shape. Also, that only works if the array is not empty.

  • Related