0

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

typescriptlang.org/play

1
  • Please provide a working example as a link to typescriptlang.org/play Commented Nov 30, 2022 at 8:39

1 Answer 1

2

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

Playground

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

1 Comment

You are my hero! I learned a lot just from your code, thanks!!

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.