简体   繁体   中英

Create key/value type from object type

Let's say we have a type

type Foo = {
  a: string;
  b: number;
  c: boolean;
}

I now want to define a type for an object having a key and a value of a given type T , so that the type of the value is directly inferred from the key, so that I can do:

const kv1: KeyValue<Foo> = {key: 'a', value: 'STRING'};
const kv2: KeyValue<Foo> = {key: 'b', value: 42};
const kv3: KeyValue<Foo> = {key: 'c', value: true};

If I simply do this:

type KeyValue<T> = {
  key: keyof T;
  value: T[keyof T]
}

... then obviously the values will be the union of all properties' values in Foo :

And if I do this:

type KeyValue<T, K extends keyof T> = {
  key: K;
  value: T[K]
}

... then sure, if explicitly typed as KeyValue<Foo, 'a'> I can create object literals that match the type of Foo :s properties, but if I don't specifically provide the type AND the key for each literal but simply do KeyValue<Foo, keyof Foo> , each value will be allowed to be of the type of any of the values in Foo , ie, all values will be string | number | boolean string | number | boolean string | number | boolean instead of being inferred from the entered key .

In the end I want to be able to do things like this:

const kvs: Array<KeyValue<Foo>> = [
  {key: 'a', value: 'STRING'}, // should not compile if value is a number or boolean
  {key: 'b', value: 42}, // should not compile if value is a string or boolean
  {key: 'c', value: true}, // should not compile if value is a string or number
];

Is it possible to derive the KeyValue type with these constraints, so that effectively it becomes KeyValue<Foo, 'a'> | KeyValue<Foo, 'b'> | KeyValue<Foo, 'c'> KeyValue<Foo, 'a'> | KeyValue<Foo, 'b'> | KeyValue<Foo, 'c'> KeyValue<Foo, 'a'> | KeyValue<Foo, 'b'> | KeyValue<Foo, 'c'> in the second example above, without having to manually write this union? Basically I guess what I'd like is to be able to infer the type of value from the value of key in the current literal object.

You cannot represent each key-value pair using the single KeyValue object, since this would require either partial inference (for the key) or somehow referring to the key property from value property inside the same object.

However, what you could do is create a discriminated union using the input object.

type Foo = {
  a: string;
  b: number;
  c: boolean;
}

type KeyValue<T> = {
  [P in keyof T]: {
    key: P;
    value: T[P];
  }
}[keyof T];

// with KeyValue<Foo>, this generates the following type
// 
// {
//     key: "a";
//     value: string;
// } | {
//     key: "b";
//     value: number;
// } | {
//     key: "c";
//     value: boolean;
// }

const kvs: Array<KeyValue<Foo>> = [
  {key: 'a', value: 40}, // ERROR
  {key: 'b', value: true}, // ERROR
  {key: 'c', value: "foo"}, // ERROR
];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM