Home > front end >  Types object base on arras of another object
Types object base on arras of another object

Time:10-17

i have a next code:

const arr = [
 {
   name: 'john',
   age: '20'
 },
 {
   name: 'anna',
   age: '30'
 }
]

Now i create a new object based at arr:

const obj = {} // {john:20,anna:30}
arr.forEach(o => obj[o.name]=o.age)

How can i dynamical type this obj, need interface like this:

interface IObj {
 john: number,
 anna: number
}

CodePudding user response:

You can literaly infer all keys and values like here:

const arr = [
  {
    name: 'john',
    age: '20'
  },
  {
    name: 'anna',
    age: '30'
  }
]

type Elem = {
  name: string;
  age: `${number}`
}

type Callback<Item> =
  (Item extends { name: infer Name, age: infer Age }
    ? (Name extends PropertyKey
      ? Record<Name, Age>
      : never)
    : never
  )

type Reduce<List extends ReadonlyArray<any>, Acc extends Record<string, `${number}`> = {}> =
  (List extends []
    ? Acc
    : (List extends [infer Head, ...infer Tail]
      ? Reduce<Tail, Acc & Callback<Head>>
      : never)
  )

const builder = <
  Name extends string,
  Age extends `${number}`,
  Item extends { name: Name, age: Age },
  List extends ReadonlyArray<Item>
>(list: [...List]) =>
  list.reduce((acc, elem) => ({
    ...acc,
    [elem.name]: elem.age
  }), {} as Reduce<List>)

const result = builder([
  {
    name: 'john',
    age: '20'
  },
  {
    name: 'anna',
    age: '30'
  }
])

result.anna // 30
result.john // 20

Playground

I have used several extra generics, Name, Age, Item and List to infer each property. Think about it as about destructure. If you want to infer some nested prop in TypeScript, you should create for it generic.

Reduce - is an utility type. It recursively iterates over the argument, and folds each element. Just like you did in your implementation. Except, you used forEach whereas I have used reduce. It just a matter of preference.

If you think above version is an overhead or overengineering, you can use this:


type Elem = {
  name: string;
  age: `${number}`;
}

const builder = <

  List extends ReadonlyArray<Elem>
>(list: List) =>
  list.reduce((acc, elem) => ({
    ...acc,
    [elem.name]: elem.age
  }), {} as Record<string, `${number}`>)

const result = builder([
  {
    name: 'john',
    age: '20'
  },
  {
    name: 'anna',
    age: '30'
  }
])

result.anna // 30
result.john // 20
  • Related