1

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?

2 Answers 2

3

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}
Sign up to request clarification or add additional context in comments.

Comments

1

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.

2 Comments

This allows to have objects like const obj2 : desiredShape = {filter: "age", value: "Vienna"}, which is likely not what the OP wants (I think the OP either wants one version or the other one, but not a mix of those two)
Good catch, updated the answer with something more restrictive +1

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.