简体   繁体   中英

TypeScript: Look up field's type based on another field

Let's say I have an interface like this:

interface Cat {
  weight: number;
  name: string;
  adoptable: boolean;
}

I want to define the following interface that can be used to set values on Cat :

interface CatUpdate {
  field: keyof Cat;
  value: ???
}

and I want TypeScript to enforce that value is the appropriate type based on field. Eg, this should be invalid:

const x: CatUpdate = {
  field: "name",
  value: 12
}

How do I define the type of CatUpdate.value ?

One way is to use a generic type:

interface CatUpdate<T extends keyof Cat> {
  field: T;
  value: Cat[T];
}

It works, but it means you'll need to be a little bit more verbose when creating the object.

// Error
const x: CatUpdate<"name"> = {
  field: "name",
  value: 12
}

// No error
const y: CatUpdate<"name"> = {
  field: "name",
  value: "Dave"
}

One solution would be to use a function for instantiation which can infer the type based on the parameters:

function getCatUpdate<T extends keyof Cat>(field: T, value: Cat[T]): CatUpdate<T> {
    return {
        field,
        value
    }
}

Which you could call like:

// Error
const x = getCatUpdate("name", 12);

// No error
const y = getCatUpdate("name", "Dave");

My inclination would be to make CatUpdate a union type and not an interface, like this:

interface Cat {
  weight: number;
  name: string;
  adoptable: boolean;
}

type PropUpdate<T extends object> = {
  [K in keyof T]: { field: K; value: T[K] }
}[keyof T];


type CatUpdate = PropUpdate<Cat>;
/* 
type CatUpdate = {
    field: "weight";
    value: number;
} | {
    field: "name";
    value: string;
} | {
    field: "adoptable";
    value: boolean;
}
*/

That should work well enough on your mentioned use case:

const x: CatUpdate = {
  field: "name",
  value: 12
} // error!

And you should be able to compose those ( CatUpdate | DogUpdate ) if you need to, although without a particular use case it's hard to know if such composition is appropriate (Can Cat and Dog be discriminated from each other? That is, is Cat & Dog impossible? If Cat & Dog is possible, then CatUpdate | DogUpdate can only be used to update a Cat & Dog . If it's impossible, then CatUpdate | DogUpdate should probably be replaced with a discriminated union instead like (CatUpdate & {kind: "CatUpdate"}) | (DogUpdate & {kind: "DogUpdate"}) .)

Hope that helps; good luck!

Link to code

Unfortunately, TypeScript does not have what you are looking for, but you can hardcode the types:

type CatUpdate 
    = { field: 'weight', value: number }
    | { field: 'name', value: string }
    | { field: 'adoptable', value: boolean }

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