Home > Software engineering >  how to convert readonly string[] generic into known object
how to convert readonly string[] generic into known object

Time:01-06

I'm trying to convert a generic readonly string[] into an object with known keys. But I encounter the error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Record<T[number], number>'.
  No index signature with a parameter of type 'string' was found on type 'Record<T[number], number>'.

Code:

class ArrayToObject<T extends readonly string[]> {
  constructor (private keys : T) {}

  GET_OBJECT () {
    const all = {} as Record<T[number], number>
    for (const index in this.keys) {
      all[this.keys[index]] = index
    }
    return all;
  }
}

What I basically want from the generic is something similar to this:

const a = ["a", "b"] as const
const o : Record<typeof a[number], number> = {
  a : 1,
  b : 2
}

playground

CodePudding user response:

You can get the string union from your readonly string using type inference, like this:

const a = ["a", "b"] as const;

type StringFromReadonlyArray<T extends readonly string[]> = T extends readonly (infer U)[] ? U : string;

const tooMany: Record<StringFromReadonlyArray<typeof a>, number> = {
  a: 1,
  b: 2,
  c: 3, // <-- Error: Type '{ a: number; b: number; c: number; }' is not assignable to type 'Record<"a" | "b", number>'.
};

const notEnough: Record<StringFromReadonlyArray<typeof a>, number> = { // <-- Error: Property 'b' is missing in type '{ a: number; }' but required in type 'Record<"a" | "b", number>'.
  a: 1,
};

const justRight: Record<StringFromReadonlyArray<typeof a>, number> = {
  a: 1,
  b: 2,
};

TypeScript Playground

That StringFromReadonlyArray utility type extracts the string union from your readonly array if possible, otherwise it resolves to the generic string type.

CodePudding user response:

It is because the type system can only know T extends readonly string[] but it cannot infer its elements' type correctly. Defining T as the element type can help the type system to infer it.

Also, the for...in statement will result in a string key value. You need to convert it into number or use Array.keys() or Array.entries() to get the numeric index.

class ArrayToObject<T extends string> {
  constructor (private keys : readonly T[]) {}
  GET_OBJECT () {
    const all = {} as Record<T, number>
    for (const [index, value] of this.keys.entries()) {
      all[value] = index //   1 if you want 1-based
    }
    return all;
  }
}

playground

  •  Tags:  
  • Related