I am stuck here:
I have an interface Field
which has a name and a value of some unknown type.
interface Field<T> {
name: string;
value: T;
}
I then have a function form
, which takes any amount of Field
's as rest parameters and returns the data they hold.
function form<T extends Field<unknown>[]>(
...fields: T
): { [k in T[number]['name']]: T[number]['value'] } {
let data = {};
fields.forEach((field) => (data[field.name] = field.value));
return <{ [k in T[number]['name']]: T[number]['value'] }>data;
}
It look like this is action:
const age: Field<number> = { name: 'age', value: 30 };
const sex: Field<string> = { name: 'sex', value: 'men' };
const data = form(age, sex);
// { age: 30, sex: 'men' }
In this example the types of age
and sex
are just the union of all fields.
data.age; // number | string
data.sex; // number | string
What i want is data to be of type:
const data: { age: number, sex: string } = form(age, sex);
But this returns the error ts(2451)
.
What does the return type of form
need to be.
Is this even possible?
(I'm using typescript version 4.9.3, the latest as of 2022-11-29)
[Edit 2022-11-30]: add Link to working example
Your Field
should have a ganegic on its name
also
interface Field<K extends string, V> {
name: K;
value: V;
}
function ensureField<K extends string, V>(field: Field<K, V>) {
return field;
}
type FieldListToRecord<List extends Field<any, any>[], O extends {} = {}> =
| List extends [infer F extends Field<any, any>, ...infer L extends Field<any, any>[]] ?
FieldListToRecord<L, O & (
F extends Field<infer K, infer V> ? { [k in K]: V }
: never
)>
: { [K in keyof O]: O[K] }; // <- Pure<T>
function form<T extends Field<any, any>[]>(
...fields: T
): FieldListToRecord<T> {
return Object.fromEntries(
fields.map(({ name, value }) => [name, value])
);
}
const age = ensureField({ name: 'age', value: 30 });
// ^?
const sex = ensureField({ name: 'sex', value: 'men' });
// ^?
const data = form(age, sex);
// ^?
// { age: 30, sex: 'men' }