Typescript: type check that record can be constructed out of a list of pieces of record
I want something like this function:
<RecordType>(list: Partial<RecordType>[]) => RecordType
How can I type check that all elements in the list will correspond to one and only one piece of the record?
Example:
type Person = {
id: string;
name: string;
lastname: string;
};
fromPieces<Person>([
{ name: "oscar" },
{ id: "arst" },
{ lastname: "mendez" }
]) => ({
name: "oscar",
id: "arst",
lastname: "mendez"
});
Solution 1:
As already explained in my comment I think that this is not possible as you can not statically typecheck that you put every "Partial" of the "Type" into the function to guarantee that you can build a whole "Type" of it.
But you can write a generic function aggregating the parts to a more complete Partial.
I don't know it this is the direction you want to go but this is a simple example:
const builder = <T> (...parts: Partial<T>[]):Partial<T> =>
parts.reduce((acc, part) => ({...acc, ...part}), {})
builder<{x: number, y: number}>({x: 5}, {y: 3}) // this works
builder<{x: number, y: number}>({x: 5}, {z: 3}) // this gives an error
Solution 2:
It's possible with recursive types but its order dependen.
You need to cast Person to Unions, so with EntriesOf I convert Person
to: { id: string } | { name: string } | { lastname: string }
And then you can cast the union to an array with a requirement for each type.
So UnionToTuple
converts it to [{ id: string }, { name: string }, { lastname: string }]
With that, you can ensure that every property is present on the input, but it also order dependent(this can be fixed tho).
type EntriesOf<Type> = { [Key in keyof Type]: Pick<Type, Key> }[keyof Type];
type UnionToTuple<T> = (
(
(
T extends any
? (t: T) => T
: never
) extends infer U
? (U extends any
? (u: U) => any
: never
) extends (v: infer V) => any
? V
: never
: never
) extends (_: any) => infer W
? [...UnionToTuple<Exclude<T, W>>, W]
: []
);
type Person = {
id: string;
name: string;
lastname: string;
};
function fromPieces<T>(arg: UnionToTuple<EntriesOf<T>>): T {
return {} as T;
}
// this works
const test = fromPieces<Person>([
{ id: "" },
{ name: "" },
{ lastname: "test" }
])
// this wont work
const test1 = fromPieces<Person>([
{ name: "" },
{ id: "" },
{ lastname: "test" }
])
See it in action
Credits to https://github.com/microsoft/TypeScript/issues/13298#issuecomment-707364842 for the UnionToTuple
type.