1

I'm looking to convert a generic T extends string[] to a ValueOf<T> - which would be a union of all possible values in the generic array.



type ValueOf<T extends Array<any>> = unknown;

class Foo<T extends string> {
  public value: T;

  constructor(value: T) {
    this.value = value;
  }
}

/**
 * Transforms foo type to allow new values and sets foo value to the first element
 * of newAllowedValues if the current value does not match it.
 */
function transform<T extends string[]>(foo: Foo<string>, newAllowedValues: T): Foo<ValueOf<T>> {
  if (newAllowedValues.length === 0) {
    throw new Error('');
  }

  if (newAllowedValues.indexOf(foo.value) === -1) {
    foo.value = newAllowedValues[0];
  }

  return foo as unknown as Foo<ValueOf<T>>;
}

let foo1 = new Foo<'a' | 'b'>('a');

let foo2 = transform(foo1, ['b', 'c']); // now foo2 should be a Foo<'b' | 'c'>

Playground

Currently I could only find a solution for the case where array is a constant: Typescript derive union type from tuple/array values

I know that I could achieve this by using an object rather than an array as newAllowedValues and relaying on keyof operator - but it's not pretty and I want to use this for a function that will be frequently used.

Maciej Krawczyk
  • 14,825
  • 5
  • 55
  • 67

1 Answers1

0

The solution I linked earlier works for this case as well, I overlooked how it could be applied to a generic.

The searched ValueOf type is:

type ValueOf<T extends readonly string[]> = T[number];

and the array in the argument has to be passed as a const:

let foo2 = transform(foo1, ['b', 'c'] as const);

Full code:

type ValueOf<T extends readonly string[]> = T[number];

class Foo<T extends string> {
  public value: T;

  constructor(value: T) {
    this.value = value;
  }
}

/**
 * Transforms foo type to allow new values and sets foo value to the first element
 * of newAllowedValues if the current value does not match it.
 */
function transform<T extends readonly string[]>(foo: Foo<string>, newAllowedValues: T): Foo<ValueOf<T>> {
  if (newAllowedValues.length === 0) {
    throw new Error('');
  }

  if (newAllowedValues.indexOf(foo.value) === -1) {
    foo.value = newAllowedValues[0];
  }

  return foo as unknown as Foo<ValueOf<T>>;
}

let foo1 = new Foo<'a' | 'b'>('a');

let foo2 = transform(foo1, ['b', 'c'] as const); // now foo2 should be a Foo<'b' | 'c'>

Playground

Maciej Krawczyk
  • 14,825
  • 5
  • 55
  • 67