Home > Enterprise >  How do I write an IndexOf<Array, T> type in Typescript?
How do I write an IndexOf<Array, T> type in Typescript?

Time:10-24

I have a readonly array of strings and numbers. I want to have a reverse map object similar to how reverse mapping of enums work. In order to do this, I need to be able to find the index of T within array A.

const myArray = [1, '1 ', 2, 3, '3 '] as const;

type MyArrayIndexes = {
  [K in typeof myArray[number]]: IndexOf<typeof myArray, K>;
};

const myArrayIndexes: MyArrayIndexes = {
  1: 0,
  '1 ': 1,
  2: 2,
  3: 3,
  '3 ': 4,
};

I was having trouble figuring out the definition of IndexOf<A extends readonly any[], T extends A[number]>, but thought I'd play with it a bit longer before I asked SO. I figured it out, so I thought I'd share in case anyone else looked to do this.

My initial implementation was something like this, though originally it was using typeof myArray directly rather than the template parameter A and included some boneheaded, uninteresting mistakes.

type IndexOf<A extends readonly unknown[], T extends A[number]> = Extract<
  {
    [K in keyof A & number]: [A[K], K];
  }[keyof A & number], 
  [T, number]
>[1];

When I tried to instantiate myArrayIndexes, it was expecting every value to be never.

CodePudding user response:

Final implementation

type ParseNum<S extends string> = S extends `${infer N extends number}` ? N : never;

type IndexOf<A extends readonly unknown[], T extends A[number]> = Extract<
  {
    [K in keyof A & `${number}`]: [
      A[K],
      ParseNum<K>
    ];
  }[keyof A & `${number}`],
  [T, number]
>[1];

Explanation

There were two things I needed to fix.

  1. keyof on a readonly array returns a string, not a number, so my [K in keyof A & number] was returning never.

  2. I then needed a method of extracting the number, which someone else had asked here. This is ParseNum<S> in my solution above.

I had also thought that the tuples needed to be readonly, but that turned out to be unnecessary.

CodePudding user response:

Here's a solution using a recursive type:

type Simplify<T> = {[K in keyof T]: T[K]}
type ReverseMapping<T extends readonly PropertyKey[]> = Simplify<_ReverseMapping<T, [], {}>>

type _ReverseMapping<Tail extends readonly unknown[], Count extends 0[], Acc>
    = Tail extends readonly [infer A, ...infer B]
    ? _ReverseMapping<B, [0, ...Count], Acc & Record<A & PropertyKey, Count['length']>>
    : Acc

Confirming that it works: (you can get rid of the Simplify part if you don't need the resulting type to be readable.)

type Test = ReverseMapping<typeof myArray>
// type Test = {
//     1: 0;
//     "1 ": 1;
//     2: 2;
//     3: 3;
//     "3 ": 4;
// }

Usage:

const myArray = [1, '1 ', 2, 3, '3 '] as const;
const myArrayIndexes: ReverseMapping<typeof myArray> = {
  1: 0,
  '1 ': 1,
  2: 2,
  3: 3,
  '3 ': 4,
};

Playground Link

  • Related