AKG
AKG

Reputation: 3286

Infer values from object

How do I infer all values from an object like this…

const fruits = {
  a: 'apple',
  b: 'banana'
};

…so that I get this?

type fruitValues = 'apple' | 'banana';

(I believe I'm looking for the equivalent of $Values<T> in Flow)

Upvotes: 3

Views: 118

Answers (1)

jcalz
jcalz

Reputation: 327994

You can infer the type from the object, but the object has already been inferred to hold only string properties. That is, the types "apple" and "banana" have been widened to string, as mentioned in the relevant GitHub issue, because they are non-readonly properties.

const fruits = {
  a: 'apple',
  b: 'banana'
};

type FruitValues = (typeof fruits)[keyof typeof fruits]; // string

So the above FruitValues is not what you want. If you want to get closer, you need to prevent that widening. One way is to use redundant but self-contained type assertions:

const fruits = {
  a: 'apple' as 'apple',
  b: 'banana' as 'banana'
};

type FruitValues = (typeof fruits)[keyof typeof fruits]; // "apple" | "banana"

Another way is to make a helper function that infers narrower types:

// put in a library somewhere
type Narrowable = string | number | boolean | undefined | null | void | {};
const narrowObj = <V extends Narrowable, T extends { [k: string]: V }>(t: T) => t;

const fruits = narrowObj({
  a: 'apple',
  b: 'banana'
})

type FruitValues = (typeof fruits)[keyof typeof fruits]; // "apple" | "banana"

Finally, once TypeScript 3.4 lands there will be const contexts which will infer the narrowest possible type (including making all the properties readonly which you might not want), like this:

const fruits = {
  a: 'apple',
  b: 'banana'
} as const; // requires TS3.4+

type FruitValues = (typeof fruits)[keyof typeof fruits]; // "apple" | "banana"

Okay, hope that helps. Good luck!

Upvotes: 7

Related Questions