HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptCritical

Is there a `valueof` similar to `keyof` in TypeScript?

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
typescriptkeyofsimilarvalueofthere

Problem

I want to be able to assign an object property to a value given a key and value as inputs yet still be able to determine the type of the value. It's a bit hard to explain so this code should reveal the problem:

type JWT = { id: string, token: string, expire: Date };
const obj: JWT = { id: 'abc123', token: 'tk01', expire: new Date(2018, 2, 14) };

function print(key: keyof JWT) {
    switch (key) {
        case 'id':
        case 'token':
            console.log(obj[key].toUpperCase());
            break;
        case 'expire':
            console.log(obj[key].toISOString());
            break;
    }
}

function onChange(key: keyof JWT, value: any) {
    switch (key) {
        case 'id':
        case 'token':
            obj[key] = value + ' (assigned)';
            break;
        case 'expire':
            obj[key] = value;
            break;
    }
}

print('id');
print('expire');
onChange('id', 'def456');
onChange('expire', new Date(2018, 3, 14));
print('id');
print('expire');

onChange('expire', 1337); // should fail here at compile time
print('expire'); // actually fails here at run time


I tried changing value: any to value: valueof JWT but that didn't work.

Ideally, onChange('expire', 1337) would fail because 1337 is not a Date type.

How can I change value: any to be the value of the given key?

Solution

UPDATE: Looks like the question title attracts people looking for a union of all possible property value types, analogous to the way keyof gives you the union of all possible property key types. Let's help those people first. You can make a ValueOf analogous to keyof, by using indexed access types with keyof T as the key, like so:

type ValueOf = T[keyof T];


which gives you

type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf; // string | number


ALSO: If you're creating an object literal and need the union of those property values as literal types like "0" instead of string, then you might want to use a const assertion; see @Dima's answer.

For the question as stated, you can use individual keys, narrower than keyof T, to extract just the value type you care about:

type sameAsString = Foo['a']; // look up a in Foo
type sameAsNumber = Foo['b']; // look up b in Foo


In order to make sure that the key/value pair "match up" properly in a function, you should use generics as well as indexed access types, like this:

declare function onChange(key: K, value: JWT[K]): void; 
onChange('id', 'def456'); // okay
onChange('expire', new Date(2018, 3, 14)); // okay
onChange('expire', 1337); // error. 1337 not assignable to Date


The idea is that the key parameter allows the compiler to infer the generic K parameter. Then it requires that value matches JWT[K], the indexed access type you need.

Code Snippets

type ValueOf<T> = T[keyof T];
type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf<Foo>; // string | number
type sameAsString = Foo['a']; // look up a in Foo
type sameAsNumber = Foo['b']; // look up b in Foo
declare function onChange<K extends keyof JWT>(key: K, value: JWT[K]): void; 
onChange('id', 'def456'); // okay
onChange('expire', new Date(2018, 3, 14)); // okay
onChange('expire', 1337); // error. 1337 not assignable to Date

Context

Stack Overflow Q#49285864, score: 840

Revisions (0)

No revisions yet.