Home > OS >  Typescript Generic to map an array of const objects to a list of types
Typescript Generic to map an array of const objects to a list of types

Time:01-29

I have some JSON data that I've strongly typed with an as const assertion.

const myList = [
  { type: 'uint256'},
  { type: 'address'}
] as const;

Now I want to convert this to the following tuple type:

[number, string]

Basically, if the type is "address" it should resolve to string.

If the type is a "uint256", it should resolve to number.

I intend to use this type to create arrays of data that conform to this schema, eg:

const foo: ConvertToTuple<typeof myList> = [1, 'asdf'];

I'm struggling to write a generic that can do this. Here's where I'm at so far:

type ConvertToTuple<
  DataObjects extends readonly { type: "address" | "uint256" }[]
> = DataObjects extends readonly [infer CurrentObject, ...infer RestObjects]
  ? CurrentObject["type"] extends "address"
    ? [string, ...ConvertToTuple<RestObjects>]
    : CurrentObject["type"] extends "uint256"
    ? [number, ...ConvertToTuple<RestObject>]
    : never
  : [];

I don't think I'm using infer correctly here. Typescript is throwing a couple of errors:

Type '"type"' cannot be used to index type 'CurrentObject'

and

Type 'RestObjects' does not satisfy the constraint 'readonly { type: "address" | "uint256"; }[]'.
  Type 'unknown[]' is not assignable to type 'readonly { type: "address" | "uint256"; }[]'.
    Type 'unknown' is not assignable to type '{ type: "address" | "uint256"; }'.

Any help untangling this is much appreciated!

CodePudding user response:

TypeScript added extends constraints on infer type variables in version 4.7, so you can simplify the recursive mapped type utility like this:

TS Playground

type ListType = "address" | "uint256";
type ListElement = { type: ListType };

type Transform<Tup extends readonly ListElement[]> =
  Tup extends readonly [
    infer H extends ListElement,
    ...infer R extends readonly ListElement[]
  ]
    ? [
      H["type"] extends "address" ? string : number,
      ...Transform<R>
    ]
    : Tup;

const myList = [
  { type: "uint256" },
  { type: "address" },
] as const satisfies readonly ListElement[];

type Foo = Transform<typeof myList>;
   //^? type Foo = [number, string]

const foo: Foo = [1, "asdf"]; // ok


If your enumeration of possible string literal types ends up growing, you can also use a type mapping of string literals to their corresponding types like this:

TS Playground

type TransformTypeMap = {
  address: string;
  uint256: number;
};

type Transform<Tup extends readonly ListElement[]> =
  Tup extends readonly [
    infer H extends ListElement,
    ...infer R extends readonly ListElement[]
  ]
    ? [TransformTypeMap[H["type"]], ...Transform<R>]
    : Tup;
  • Related