Consider the following function (playground link):
function objectify<T extends string>(o: {[key in T]: number}) {
const output = {} as {[key in T]: {value: number}};
for (const key in o) {
output[key as T] = {value: o[key as T]}
}
return output
}
Now, I have two issues.
objectify must have all possible incoming keys instead of just only incoming keys:const data = {
first: { a: 1 },
second: { b: 2 }
}
function objectifyDatum(k: keyof typeof data) {
// Argument of type '{ a: number; } | { b: number; }'...
return objectify(data[k])
// is not assignable to parameter of type '{ a: number; b: number; }'.
}
objectify is similarly an object with all keys present in the incoming union type, instead of a union of objects.// has type { a: { value: number }; b: { value: number } },
// but I want { a: { value: number } } | { b: { value: number } }
const objectifiedDatum = objectifyDatum('first')
These seem like similar issues, revolving around the fact that TypeScript is merging the keys in the union { a: number } | { b: number } into one larger object with keys a and b.
The solution to this would be to “distribute” the key type over a union of object types, but I don't know how to tell TypeScript this is what I want.
The issue is related to the generic type, because you are not passing a generic object but you are using the generics on the keys of such object.
A simple solution is to pass directly the generic object:
function objectify<T extends { [Key: string]: number }>(o: T) {
const output = {} as {[key in keyof T]: {value: number}};
for (const key in o) {
output[key] = { value: o[key] }
}
return output
}
With this objectifiedDatanum is correctly typed as
const objectifiedDatum: {
a: {
value: number;
};
} | {
b: {
value: number;
};
}