0

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.

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Since we already know that the type will always be string then better to use Record<string, string>
@decpk yes, that's the way I added a note. The original function is just for strings, but it works for any kind of values.
0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.