allow these kind of types:
type Type<T> = {
x: keyof T;
value: T[x]
}
type of property value is based on value inside property x.
T[x] is only allowed when type of x extends keyof T and keyof T extends x (x = keyof T)
image a use case like this:
type Filter<T> = {
fieldName: keyof T;
operator: "like" | "equal" | "notLike" | "notEqual",
value: T[keyof T]
}
type Query<Model> = {
filters: Filter<Model>[]
};
const model = {
stringProp: "str",
intergerProp: 123,
booleanProp: true as const
}
const query: Query<typeof model> = {
filters: [
{
fieldName: "stringProp",
operator: "equal",
value: "test"
}
]
}
approach above works but has following problems:
fieldName: "stringProp" type of value should be string but when we pass a number to value it doesn't give an error because typeof model.integerProp is number."option1" | "option2" and another property's type is string, it ignores the enum and it doesn't give a proper autocompletion.we can do something with functions like this:
const createFilter = <T, K extends keyof T>(obj: T, fieldName: K, value: T[K]): Filter<T,K> => ({
fieldName,
value,
})
but the problem with this approach is:
__do_not_use_directly_use_create_filter_instead__ which is... not ideal. )related stackoverflow question : https://stackoverflow.com/questions/59857131/how-to-define-type-of-a-property-based-on-another-propertys-value/59857346#comment105847035_59857346
My suggestion meets these guidelines:
Looks like you want an existential type (#14466) (e.g., Filter<T> for some K extends keyof T). TS doesn't have direct support for arbitrary existential types, but in cases where you're only interested in an enumerable set of types (like keyof T for types T with known literal keys) you can represent this as a union:
type Filter<T> = { [K in keyof T]: {
fieldName: K;
operator: "like" | "equal" | "notLike" | "notEqual",
value: T[K]
} }[keyof T]
Here's how it looks with something like your example:
const model = {
stringProp: "str",
hamburgerProp: 123,
booleanProp: true
}
type FilterForModel = Filter<typeof model>;
/* type FilterForModel = {
fieldName: "stringProp";
operator: "like" | "equal" | "notLike" | "notEqual";
value: string;
} | {
fieldName: "hamburgerProp";
operator: "like" | "equal" | "notLike" | "notEqual";
value: number;
} | {
fieldName: "booleanProp";
operator: "like" | "equal" | "notLike" | "notEqual";
value: boolean;
} */
You can see that Filter<typeof model> is a union of the three types you want to accept and you don't need a feature addition to the language. Is there a different motivating use case for this?
@jcalz amazing! I didn't know about this.
thank you, no i will close the issue.
Most helpful comment
Looks like you want an existential type (#14466) (e.g.,
Filter<T>for someK extends keyof T). TS doesn't have direct support for arbitrary existential types, but in cases where you're only interested in an enumerable set of types (likekeyof Tfor typesTwith known literal keys) you can represent this as a union:Here's how it looks with something like your example:
You can see that
Filter<typeof model>is a union of the three types you want to accept and you don't need a feature addition to the language. Is there a different motivating use case for this?