Home > Blockchain >  Get type literal of tuple value in array of tuples
Get type literal of tuple value in array of tuples

Time:01-18

Given array

const arr = [[0, 'foo'], [1, 'bar']] as const;

I need value to be concrete literal value, not string or 'foo' | 'bar'.

const value = get(arr, 0); // value type: 'foo'

My best try was

type Entry<K, V> = readonly [K, V];
type GetType<A extends readonly Entry<any, any>[], K> = A extends readonly Entry<K, infer V>[] ? V : never;

function get<K extends PropertyKey, V extends string>(arr: readonly Entry<K, V>[], id: K): GetType<typeof arr, K> {
  return new Map(arr).get(id)!;
}

const arr = [[0, 'foo'], [1, 'bar']] as const;
const val = get(arr, 0);

But it results in val type being 'foo' | 'bar'.

CodePudding user response:

I'd recommend writing get() so that the relationship between the generic type parameters and the function parameters are as straightforward as necessary to get the proper inference. So instead of having arr be of a type related to two separate type parameters, I'd suggest make arr be of a type related to just one type parameter in a simple way, and have id be related to the other one.

For example:

function get<T extends readonly [PropertyKey, string], K extends T[0]>(
    arr: readonly T[], id: K
): Extract<T, readonly [K, any]>[1] {
    return new Map(arr).get(id)!;
}

Here the type of arr is a (possibly readonly) array of elements of type T, which is constrained to a (possibly readonly) entry tuple type. So if you pass in get([[k1, v1], [k2, v2], [k3, v3]], ...) then T should be inferred as the equivalent of the union type [typeof k1, typeof v1] | [typeof k2, typeof v2] | [typeof k3, typeof v3]. And the type of id is K which is constrained to be one of the keys in the T union of tuple types (which you get by indexing into T with an index of 0).

To determine the return type, we need to take the union T and Extract the union member whose key corresponds to K. Once we do that we get the value type from that entry, by indexing into it with an index of 1.

Let's test it out:

const val = get(arr, 0);
// const val: "foo"
const val2 = get(arr, 1);
// const val2: "bar"

Looks good.

Playground link to code

  • Related