1

So I'm testing TypeScript in how far I can take it and cannot seem to get past the following issue. How can I limit the type of property B when property A has some value?

// The type I want to declare
type Bar<T> = {
    prop: keyof T; // Select a property of the type
    value: T[keyof T]; // Provide the value of that property, this currently does not work
}

// Some random interface
interface Foo {
    id: number;
    name: string;
}

let bar: Bar<Foo> = {
    prop: "name", // Selected Foo.name: string
    value: 9,     // Should only allow strings
};

The property type of value in this case is number | string, but I would like to force it to string since the selected property name is of type string.


Notes

I can declare it this way, but the interface is a lot less appealing, clear and is more error prone: only one property should be selectable and you don't really know what is expected since property names are absent. Or I need to nest objects even further.

type Bar<T> = {
    prop: {
        [K in keyof T]?: T[K];
    }
}

let bar: Bar<Foo> = {
    prop: {
        name: 'yay', // string is forced now
    }
};

  • Related question. I guess this only works when the values are known at compile time.
Didii
  • 1,194
  • 12
  • 36

2 Answers2

0

It's because Foo has two keys one of type string and number the other. Therefore value is string | number. Maybe this works?

// The type I want to declare
type Bar<T, U> = {
    prop: keyof T; // Select a property of the type
    value: U; // Provide the value of that property, this currently does not work
}

// Some random interface
interface Foo {
    id: number;
    name: string;
}

let bar: Bar<Foo, Foo['name']> = {
    prop: "name", // Selected Foo.name: string
    value: '9',     // Should only allow strings
};
CyberProdigy
  • 737
  • 9
  • 20
  • Yeah, I thought about something similar, but `Foo['name']` is not known at compile time yet. Also, now `name` is duplicated which is also not really what I'd want. – Didii Aug 23 '20 at 22:33
0

Based on your usage example:

let bar: Bar<Foo> = {
    prop: "name", // Selected Foo.name: string
    value: 9,     // Should only allow strings
};

the compiler cannot infer we want the name property. It need another type parameter, as Pick utility type.

type PickEntry<T, K extends keyof T> = {
    prop: K; // Select a property of the type
    value: T[K]; // Provide the value of that property, this currently does not work
}

Note: PickEntry is more explicit than Foo

Then, we can detect invalid value type:

let nameEntry: PickEntry<Foo, 'name'> = {
    prop: "name",
    value: 9,
//  ~~~~~ Error: Type 'number' is not assignable to type 'string'.(2322)
};
Romain Deneau
  • 2,841
  • 12
  • 24