Home > Software design >  How do I turn an array of strings into property names on the type level?
How do I turn an array of strings into property names on the type level?

Time:11-16

Given the function

function invert(arr: string[]) {
  let result = {};

  arr.forEach((x, i) => {
    result[x] = i;
  })

  return result; // insert type cast here
}

What I'm basically looking for, is a way for intellisense in VSCode to tell me exactly what props are available in the resulting object, so given const example = invert(['foo', 'bar', 'baz'])

The auto-completion should show me this: Intellisense output

My current approach involves casting the result as {[K in T[number]]: K} where T is an alias for typeof arr, but this approach doesn't give me any auto-completion.

What do I write to get TypeScript to do show me this stuff?

EDIT: It should be noted that the interface {[name]: number} is merely a simplified example (for NDA reasons), the main question of how to map an array to property names is still the core of it all.

CodePudding user response:

If you're happy to define your array using as const this is a possible solution

type ArrayToNumberMapType<K extends string, A extends readonly K[]> = {[K in A[number]]: number}

function invert<K extends string, A extends readonly K[]>(arr: A): ArrayToNumberMapType<K, A> {
  let result: ArrayToNumberMapType<K, A> = {} as ArrayToNumberMapType<K, A>;

  arr
    .forEach(
      (x: K, i: number) => {
        result[x] = i;
      }
    )

  return result;
}

const someArray = ['foo', 'bar', 'baz'] as const
const mappedObject = invert(someArray)
mappedObject. // .foo, .bar or .baz

CodePudding user response:

As the comments already noted, one way to do this is to make invert generic. Currently arr is only a string[]. That's why your mapped type does not work.

function invert<T extends string>(arr: T[]): Record<T, number> {
  let result = {} as Record<T, number>;

  arr.forEach((x, i) => {
    result[x] = i;
  })

  return result
}

This simply generic implementation produces a Record<T, number> where T consists of the keys inside the array.

const example = invert(['foo', 'bar', 'baz'])
// const example: Record<"foo" | "bar" | "baz", number>

Playground


This works but we could still improve this a bit. As currently, the keys are only typed as number even though we know exactly which number they have.

function invert<T extends string[]>(arr: [...T]): { 
    [K in keyof T & `${bigint}` as T[K] & string]: K extends `${infer N extends number}` ? N : never 
} {
  let result = {} as any;

  arr.forEach((x, i) => {
    result[x] = i;
  })

  return result
}

In this implementation, we map over the elements of T directly and filter out the number keys. Then we convert the index K to a number.

This results in the following return type:

const example = invert(['foo', 'bar', 'baz'])
// const example: {
//     foo: 0;
//     bar: 1;
//     baz: 2;
// }

Playground

  • Related