1

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!

1 Answer 1

2

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;
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.