Home > Blockchain >  How to write this generic function in TypeScript which merges array parameter and values into an obj
How to write this generic function in TypeScript which merges array parameter and values into an obj

Time:07-04

I have written a function, which merges a keys array and a values array into an object. The implementation is just like below:

function mergeToObject(keys: string[], values: string[]) {
  const object:? = {}
  for (let i = 0; i < keys.length; i  ) {
    object[keys[i]] = values[i] // TypeScript complains it
  }
  return object
}

For illustrating the function, it behaves like below:

mergeToObject(['a', 'b', 'c'], ['x', 'y', 'z']) // => { a: 'x', b: 'y', c: 'z' }

And also, I always pass keys param as a constant expression like ['a', 'b', 'c'], but not a runtime value. so I guess it must exists a generic syntax exactly declaring the return type contains keys a, b and c.

For illustrating the behavior more concretely:

const values = ['x', 'y', 'z']

// If I invoke as
const object = mergeToObject(['a', 'b', 'c'], values)
// Then the type of object should be `{ a: string, b: string, c: string }`
object.a // is ok
object.b // is ok
object.c // is ok
object.d // is not ok

// In other words, if I invoke as
const object = mergeToObject(['e', 'f', 'g'], values)
// Then the type of object should be `{ e: string, f: string, g: string }`
object.e // is ok
object.f // is ok
object.g // is ok
object.d // is not ok

So what is exactly the generic written? I'll sincerely appreciate your help.

CodePudding user response:

Use Record:

function mergeToObject(keys: string[], values: string[]) : Record<string, any> {
  const object: Record<string, any> = {}
  for (let i = 0; i < keys.length; i  ) {
    object[keys[i]] = values[i] // TypeScript complains it
  }
  return object
}

More info here: https://www.typescriptlang.org/docs/handbook/utility-types.html#example-3

Note: I used any as type for the values, but if you prefer to constrain to string only, just change to it.

CodePudding user response:

SOLUTION 1

You can also create an interface and assing it to it as:

interface mergeObject {
  [k: string]: string
}

which means that mergeObject is an object of key and value as string

interface mergeObject {
  [k: string]: string
}

function mergeToObject(keys: string[], values: string[]): mergeObject {
  const object: mergeObject = {}
  for (let i = 0; i < keys.length; i  ) {
    object[keys[i]] = values[i] // TypeScript complains it
  }
  return object
}

const result = mergeToObject(['a', 'b', 'c'], ['x', 'y', 'z']) // => { a: 'x', b: 'y', c: 'z' }
console.log(result)

SOLUTION 2

interface mergeObject {
  [k: string]: string;
}

function mergeToObject(keys: string[], values: string[]): mergeObject {
  return keys.reduce((acc: mergeObject, curr: string[], index: number) => {
    acc[curr] = values[index];
    return acc;
  }, {});
}

const result = mergeToObject(['a', 'b', 'c'], ['x', 'y', 'z']); // => { a: 'x', b: 'y', c: 'z' }
console.log(result);

CodePudding user response:

The generic solution to this problem would look like this:

function mergeToObject<
  K extends string[], 
  V extends any[],
  RT extends { 
    [I in keyof K as K[I] extends string ? K[I] : never]: V[Exclude<I, number> & keyof V] 
  }
>(keys: readonly [...K], values: readonly [...V]): RT {

  const object: Record<string, any> = {}
  for (let i = 0; i < keys.length; i  ) {
    object[keys[i]] = values[i]
  }
  return object as RT
}

We store the keys and the values arrays in the generic tuple types K and V. The return type of the function will be stored in RT. For RT we map over K and use K[I] as the key name for each property. V[I] will be used as the type for each property. Since TypeScript does not know that V can be indexed with I, we have to use this syntax: V[Exclude<I, number> & keyof V].

Some tests to see if it's working:

const object = mergeToObject(['a', 'b', 'c'], ['x', 23, new Date()])
//    ^? const object: { a: string; b: number; c: Date; }

object.a
object.b
object.c
object.d // Error: Property 'd' does not exist on type '{ a: string; b: number; c: Date; }'



const object2 = mergeToObject(['e', 'f', 'g'], [23, 'y', undefined])
//    ^? const object2: { e: number; f: string; g: undefined; }

object2.e
object2.f
object2.g
object2.d // Error: Property 'd' does not exist on type '{ e: number; f: string; g: undefined; }

Playground

  • Related