492

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?

1
  • 2
    The package type-fest (github.com/sindresorhus/type-fest) has the type ValueOf, as well as many other exceedingly useful utility types - I use it all the time, and would highly recommend it. Commented Jul 8, 2021 at 8:42

12 Answers 12

791

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> = T[keyof T];

which gives you

type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf<Foo>; // 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<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

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.

10
  • 3
    Ran into a problem using a string-valued enum with function members. To handle this well, you can use type StringValueOf<T> = T[keyof T] & string;. The best docs I found on string enums are the TypeScript 2.9 release notes
    – karmakaze
    Commented Aug 19, 2019 at 1:46
  • 2
    Another construct I've found useful is Required<T>[keyof T], which represents the values you can get from t[k] when t: T and k in t.
    – jsalvata
    Commented Sep 30, 2021 at 12:03
  • 2
    @markokraljevic the answer is perfectly valid. You can't assign types as object values as Typescript types (non-primitive types) do not exist at runtime. This solution is for creating types that can be more than one type and unify it all in one instead of copy/pasting long lists of types (string | boolean | MyType). Commented Feb 3, 2022 at 9:56
  • 3
    Don't forget to add as const to the end of the object in question if you are just getting generic string types out of this.
    – Bret
    Commented Jan 26, 2023 at 22:30
  • 1
    The thing is, this is the top answer for (at least some common) Google queries. Clearly (from the comment above and the three upvotes on it) people are coming here and being confused when this answer doesn't answer things for them. You've already updated the answer in light of this fact before; would adding a sentence or two about as const to help really be a detriment? Commented Jul 1 at 21:05
147

There is another way to extract the union type of the object:

  const myObj = {
    a: 1,
    b: 'some_string'
  } as const;

  type Values = typeof myObj[keyof typeof myObj];

Result union type for Values is 1 | "some_string"

It's possible thanks to the const assertions (as const part) introduced in TS 3.4.

3
  • 27
    This const thing is very valuable, TypeScript will actually provide the values themselves and remove duplicates; It's very good for dictionaries.
    – John
    Commented May 7, 2020 at 22:35
  • This helped me. If dealing with an enum as a type, this can be written type MyEnum = { A: 1, B: 'some_string' }; type values = MyEnum[keyof MyEnum];
    – MarkMYoung
    Commented May 5, 2022 at 20:51
  • 1
    Without as const this doesn't work. It only returns the type of the value (not the actual value) DON'T forget it.
    – Claudiu
    Commented Oct 26, 2022 at 14:51
90

If anyone still looks for implementation of valueof for any purposes, this is a one I came up with:

type valueof<T> = T[keyof T]

Usage:

type actions = {
  a: {
    type: 'Reset'
    data: number
  }
  b: {
    type: 'Apply'
    data: string
  }
}
type actionValues = valueof<actions>

Works as expected :) Returns an Union of all possible types

2
  • I like this one best as it is clear and explicit in what is being done and valueof nicely complements Typescript's existing keyof and typeof operators. It can even be used with objects rather than types, e.g. type ObjValues = valueof<typeof obj>, where obj is an associative array. Commented Jun 6, 2023 at 8:17
  • Thanks, and as a minor comment to aid any future passers-by, if anyone else is using the ts-essentials lib, you can instead use the ValueOf utility type. Commented Nov 29, 2023 at 0:32
31
+500

With the function below you can limit the value to be the one for that particular key.

function setAttribute<T extends Object, U extends keyof T>(obj: T, key: U, value: T[U]) {
    obj[key] = value;
}

Example

interface Pet {
     name: string;
     age: number;
}

const dog: Pet = { name: 'firulais', age: 8 };

setAttribute(dog, 'name', 'peluche')     <-- Works
setAttribute(dog, 'name', 100)           <-- Error (number is not string)
setAttribute(dog, 'age', 2)              <-- Works
setAttribute(dog, 'lastname', '')        <-- Error (lastname is not a property)
0
16

Try this:

type ValueOf<T> = T extends any[] ? T[number] : T[keyof T]

It works on an array or a plain object.

// type TEST1 = boolean | 42 | "heyhey"
type TEST1 = ValueOf<{ foo: 42, sort: 'heyhey', bool: boolean }>
// type TEST2 = 1 | 4 | 9 | "zzz..."
type TEST2 = ValueOf<[1, 4, 9, 'zzz...']>
1
13

You can made a Generic for your self to get the types of values, BUT, please consider the declaration of object should be declared as const, like:

export const APP_ENTITIES = {
  person: 'PERSON',
  page: 'PAGE',
} as const; <--- this `as const` I meant

Then the below generic will work properly:

export type ValueOf<T> = T[keyof T];

Now use it like below:

const entity: ValueOf<typeof APP_ENTITIES> = 'P...'; // ... means typing

   // it refers 'PAGE' and 'PERSON' to you
8

Thanks the existing answers which solve the problem perfectly. Just wanted to add up a lib has included this utility type, if you prefer to import this common one.

https://github.com/piotrwitek/utility-types#valuestypet

import { ValuesType } from 'utility-types';

type Props = { name: string; age: number; visible: boolean };
// Expect: string | number | boolean
type PropsValues = ValuesType<Props>;
1
  • This is the best answer because this answers handles array too. Other answers don't as lots of property on arrays reflect otherwise Commented Apr 11, 2022 at 0:53
4

You could use help of generics to define T that is a key of JWT and value to be of type JWT[T]

function onChange<T extends keyof JWT>(key: T, value: JWT[T]);

the only problem here is in the implementation that following obj[key] = value + ' (assigned)'; will not work because it will try to assign string to string & Date. The fix here is to change index from key to token so compiler knows that the target variable type is string.

Another way to fix the issue is to use Type Guard

// IF we have such a guard defined
function isId(input: string): input is 'id' {
  if(input === 'id') {
    return true;
  }

  return false;
}

// THEN we could do an assignment in "if" block
// instead of switch and compiler knows obj[key] 
// expects string value
if(isId(key)) {
  obj[key] = value + ' (assigned)';
}
4

with type-fest lib, you can do that with ValueOf like that:

import type { ValueOf } from 'type-fest';

export const PATH_NAMES = {
  home: '/',
  users: '/users',
  login: '/login',
  signup: '/signup',
};

interface IMenu {
  id: ValueOf<typeof PATH_NAMES>;
  label: string;
  onClick: () => void;
  icon: ReactNode;
}

  const menus: IMenu[] = [
    {
      id: PATH_NAMES.home,
      label: t('common:home'),
      onClick: () => dispatch(showHome()),
      icon: <GroupIcon />,
    },
    {
      id: PATH_NAMES.users,
      label: t('user:users'),
      onClick: () => dispatch(showUsers()),
      icon: <GroupIcon />,
    },
  ];
1
  • In this case ValueOf will only return the type of the value instead of the litterral value, so the type of id is merely a string rather then the literal union of '/' | '/users' | '/login' | '/signup'
    – vilsbole
    Commented Apr 22, 2023 at 14:09
2

I realize this is slightly off topic, That said every time I've looked for a solution to this. I get sent to this post. So for those of you looking for String Literal Type generator, here you go.

This will create a string Literal list from an object type.

export type StringLiteralList<T, K extends keyof T> = T[keyof Pick<T, K>];

type DogNameType = { name: "Bob", breed: "Boxer" } | { name: "Pepper", breed: "Spaniel" } | { name: "Polly", breed: "Spaniel" };

export type DogNames = StringLiteralList<DogNameType, "name">;

// type DogNames = "Bob" | "Pepper" | "Polly";
1
  • This doesn't answer the question. I suggest creating a new question instead.
    – styfle
    Commented Jan 12, 2023 at 21:52
1

Building on @Dima's answer

const REGISTRY = 
{
  one: MyClassOne,
  two: MyClassTwo
};

function select(value: keyof typeof REGISTRY): typeof REGISTRY[keyof typeof REGISTRY]
{
  return REGISTRY[value];
}

const myNewthing = new select('one');
-1

One-liner:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

Example on a generic method:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;

Not the answer you're looking for? Browse other questions tagged or ask your own question.