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.