user10843855
user10843855

Reputation:

Dynamic Type in TypeScript

How can I dynamically assign a type to a generic class based on the type variable's property? For example

interface EntityErrors<T> {
    [p in keyof T]?: string; // How can I make "string" dynamic?  It needs to be a string for primitives (id, name), and an array of strings for the `teachers` array.

    // I've tried the following, but it appears `teachers` is still resolving to `SimpleError` instead of `ArrayOfErrors`.
    [p in keyof T]?: p extends [] ? ArrayOfErrors<p> : SimpleErrors;
    // I've also tried `instanceof` and `typeof`, but I receive syntax errors.
    [p in keyof T]?: p instanceof [] ? ArrayOfErrors<p>: SimpleErrors;
}

interface School {
    id: string;
    name: string;
    teachers: Teacher[];
}

interface Teacher {
    id: string;
    name: string;
}

Because the School error object looks like:

{
  "id": "The input is invalid",
  "name": "The input is invalid",
  "teachers": [
    {
      "id": "The input is invalid",
      "name": "The input is invalid"
    },
    {
      "id": "The input is invalid",
      "name": "The input is invalid"
    }
  ]
}

Upvotes: 4

Views: 4494

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249636

Based on your requirements, things will get a bit more complicated, as the errors type needs to be recursive. If we encounter a primitive array or a primitive property the property in the result type will be string. If we have an entity or an entity array we need to apply the type recursively to get the appropriate result.

To do this we need to use a mapped type. The the type of property will be T[P] with P beeing the property name

type EntityErrors<T> = {
    [P in keyof T]?:ErrorProperty<T[P]>
}
type Primitive = number| string| Date | boolean
type ErrorProperty<T> =
    T extends Primitive ? string :
    T extends Array<Primitive> ? string[] :
    T extends Array<infer U> ? Array<EntityErrors<U>> :
    EntityErrors<T>;


interface School {
    id: string;
    name: string;
    teachers: Teacher[];
}

interface Teacher {
    id: string;
    name: string;
}

let e:EntityErrors<School> = {
"id": "The input is invalid",
"name": "The input is invalid",
"teachers": [
    {
    "id": "The input is invalid",
    "name": "The input is invalid"
    },
    {
    "id": "The input is invalid",
    "name": "The input is invalid"
    }
]
}

Upvotes: 0

SLaks
SLaks

Reputation: 887453

p is a string type with the name of the property.

You need to check the type of the property within your class:

    [p in keyof T]?: T[p] extends [] ? ArrayOfErrors<T[p]>: SimpleErrors;

Upvotes: 6

Related Questions