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