Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: derive union type from array of objects

I would like to declare a type-enforced array of items and be able to derive a union type from it. This pattern works if you do not explicitly give a type to the items in the array. I am not sure how to best explain it so here is an example:

EXAMPLE 1

type Pair = {
  key: string;
  value: number;
};

const pairs: ReadonlyArray<Pair> = [
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
] as const;

type Keys = typeof pairs[number]['key']

EXAMPLE 2

type Data = {
  name: string;
  age: number;
};

const DataRecord: Record<string, Data> = {
  foo: { name: 'Mark', age: 35 },
  bar: { name: 'Jeff', age: 56 },
} as const;

type Keys = keyof typeof DataRecord;

Here is an example of deriving the keys when using as const. I want this same behavior but with the array being explicitly typed.

const pairs = [
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
] as const;

type Keys = typeof pairs[number]['key']; // "foo" | "bar"

desired value of keys: "foo"|"bar"

actual value of keys: string

like image 785
Ben Avatar asked Mar 02 '20 20:03

Ben


People also ask

How do you convert an array into string literal union type in TypeScript?

To convert an array of strings into a string literal union type in TypeScript, you can first define the array of strings then make the array as readonly using the as const keyword, and then use the typeof operator on all the values contained in the array.

Does typeof work with arrays?

The typeof operator returns "object" for objects, arrays, and null.

What type is {} TypeScript?

The type object {} represents a 0-field object. The only legal value of this type is an empty object: {} .


1 Answers

The usual approaches are:

  • let TS infer the type of pairs by omitting the explicit type ReadonlyArray<Pair> (see answer)
  • give key in Pair the type "foo"|"bar"

If you don't want to do this, then the only way to infer your keys and restrict the type of pairs is to use a helper function. The Pair type will also be made generic to save the given key string literal types. You can use an IIFE to make the assignment compact:

type Pair<K = string> = {
    key: K;
    value: number;
};

const pairs = (<T>(p: readonly Pair<T>[]) => p)([
    { key: 'foo', value: 1 },
    { key: 'bar', value: 2 },
] as const) // readonly Pair<"foo" | "bar">[]

type Keys = typeof pairs[number]['key'] // "foo" | "bar"

Playground

like image 166
bela53 Avatar answered Sep 23 '22 06:09

bela53