Restricting types on object properties in TypeScript dynamically based on other properties
Asked Answered
D

1

7
type MatchOperator = "==" | ">" | "<";

type Criteria<T, P extends keyof T> = {
    field: P,
    value: T[P],
    operator: MatchOperator,
}

interface User {
    name: string;
    age: number;
    id: number;
}

const adultCriteria: Criteria<User, "age"> = {
    field: "age",
    operator: ">",
    value: 18
}

Is there a better way to restrict the type of value based on field using Typescript as mentioned below?

const adultCriteria: Criteria<User> = {
    field: "age",
    operator: ">",
    value: 18
}
Donothingism answered 28/4, 2020 at 8:44 Comment(2)
I think TypeScript typing concerns only types themselves. Values are something that is handled runtime, and type checks are handled during the compilation phase. They are two different problems. If you want to have an input validation for your incoming objects, you can use a library like falidator npmjs.com/package/@codeallnight/falidator - But if there is a good use case and way to do like you describe it in a question that would be interesting.Cellulose
The type looks good. Best you could do is to try having the second type parameter inferred from the definition of the object.Reluctant
S
8

Yeah, it's possible:

type Criteria<T> = {
    [P in keyof T]: {
        field: P,
        value: T[P],
        operator: MatchOperator
    }
}[keyof T]

This way you get a union type composite of 3 possible types:

type OnePossibleCriteria = Criteria<User>

type OnePossibleCriteria = {
    field: "name";
    value: string;
    operator: MatchOperator;
} | {
    field: "age";
    value: number;
    operator: MatchOperator;
} | {
    field: "id";
    value: number;
    operator: MatchOperator;
}

And when you assign a solid value to it, it's narrowed down to one of them.

const adultCriteria: OnePossibleCriteria = {
    field: "age",
    value: 18,
    operator: ">"
}

TypeScript Playground

Sou answered 28/4, 2020 at 8:55 Comment(3)
That's super-intelligent :)Donothingism
Sure, before I wanna clear some doubts. Can you read the criteria type out for me? I'm not used to this {}[keyof T] . What does this mean?Donothingism
The thing inside braces { [P in keyof T]: something } is a mapped type. It's basically a key-value pair struct, then you get the value part of that struct with indexing syntax {}[keyof T], that yields a union type of all possible value.Sou

© 2022 - 2024 — McMap. All rights reserved.