Home > front end >  Type object value based on another object key
Type object value based on another object key

Time:11-05

I've been through many parts of typescript docs like mapped types, generics,... but honestly couldn't find a solution.

Problem:

Assuming a have an object like this with corresponding type:

const filters: {
  age: { value: number, isActive: boolean },
  city: { value: string, isActive: boolean },
} = {
  age: { value: 25, isActive: true },
  city: { value: "tokyo", isActive: false },
};

Later in my code, I may transfer the data into this different shape:

    interface desiredShape {
          filter: // either age or city
          value: // if filter == age, should be of type number, if filter == city, should be of type string
    }

So the following assignment is correct:

const obj2 : desiredShape = {filter: "city", value: "Vienna"}

How can I achieve that type?

CodePudding user response:

You could use type with a union type:

type desiredShape = {
    filter: 'age',
    value: number
} | {
    filter: 'city',
    value: string
}
const obj2 : desiredShape = {filter: "city", value: "Vienna"}

Or with a generic parameter and a conditional type, though I don't know if it somehow can be automatically inferred:

interface desiredShape<T extends 'age' | 'city'> {
    filter: T,
    value: T extends 'age' ? number : string
}

const obj1 : desiredShape<'city'> = {filter: "city", value: "Vienna"}
const obj2 : desiredShape<'age'> = {filter: "age", value: 5}

CodePudding user response:

You can use a union of types, either as literals or other types, using the | operator.

interface desiredShape {
  filter: "age" | "city",
  value: string | number
}

See https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types for more examples.

Edit: Per A_A's comment, this allows for a mismatch between the filter and the value's respective types. If you want them to strictly match you need something more fancy:

type Filters = "age" | "city";

type DesiredFilter<T extends { value: unknown }> = {
  type: Filters
  filter: T["value"]
};

type AgeFilter  = DesiredFilter<{ value: number, isActive: boolean }>;

The type AgeFilter will now correctly restrict to the corresponding type. For brevity's sake I've omitted a separate definition for the generic type passed to DesiredFilter<T>, but you can substitute in any type of parameter that has a value property.

  • Related