medihack
medihack

Reputation: 16627

Mixed literal and array parameter type in class with generics

I have a class like this

class Schema<T extends "foo" | string[]> {
  constructor(public value: T) {}
}

for which the constructor should accept the string literal "foo" or an arbitrary array of strings. An instantiation should return an object which is typed with only the literals (also for the array as union literals):

// What I would like to have
new Schema(["x", "y"]) // => Schema<"x" | "y">
new Schema("foo") // => Schema<"foo">

This is not the case for the above class implementation (it returns an object of type Schema<string[]> when providing an array).

But if I define my class this way

class Schema<T extends "foo" | string> {
  constructor(public value: T[]) {}
}

I lose the option to call new Schema("foo").

Is there some Typescript KungFu to solve this problem?

Upvotes: 1

Views: 181

Answers (1)

Here you have most simpliest way:

class Schema<Value extends string, Values extends string[]> {

  constructor(value: Value | [...Values]) { }
}

// What I would like to have
new Schema(["x", "y"]) // Schema<string, ["x", "y"]>
new Schema("foo") // Schema<"foo", string[]>

Playground

As you have already noticed, there is a problem with extra string or string[] generic.

I think we can do better.

SInce constructor does not allow us to use generics, we can create extra method for inference purpose.

class Schema {
  register<Value extends string>(value: Value): asserts this is { value: Value }
  register<Value extends string[]>(value: [...Value]): asserts this is { value: Value[number] }
  register<Value extends string | string[]>(value: Value): asserts this is { value: Value } {


  }
}

const result: Schema = new Schema();
result.value // expected error
result.register(['x', 'y']);
result.value // x | y

Playground

Here you can find docs about asserts and here about function overloads.

Please let me know if it works for you and if it needs to be improved. THank you

Upvotes: 1

Related Questions