1

let's say we have a function toArray that takes an argument and returns it as is if it is an array and if not returns an array with the argument value:

// function that takes a value and returns the value if it is of type array and if not returns the value in an array
export const toArray = <T>(value: T | T[]): T extends undefined ? [] : Array<T> => {
  if (typeof value === "undefined") return [] as any;
  return (Array.isArray(value) ? value : [value]) as any;
};

this function works fine as long as the type of the arguments is not union:

number => number[]
number[] => number[]

for unions I got not the expected result:

// wanted
'a'|'b' => ('a'|'b')[]

// currently
'a'|'b' => 'a'[] | 'b'[]

How do I tell typescript to infer array of union instead of union of arrays?

1
  • Your return statements have as any, so does it really matter what type you get? The value that's passed as a generic is either a, or b. It's not interpreted as "union type" Commented Dec 25, 2022 at 6:11

2 Answers 2

2

As Typescript documentation says :

To avoid that behavior (distributivity), you can surround each side of the extends keyword with square brackets.

You should define the returned type of the function like this :

[T] extends [undefined] ? [] : Array<T>
Sign up to request clarification or add additional context in comments.

Comments

2

Note that TypeScript does not infer literal values from objects (including arrays). For that, you'll need to use a const assertion on object literal input values. (See examples in the code below.)

You can use generics with a function overload signature to achieve your goal.

Here's an example demonstrating the details in your question:

TS Playground

const intoArray: {
  (value?: undefined): unknown[];
  <T extends readonly unknown[]>(value: T): T[number][];
  <T>(value: T): T[];
} = (value?: unknown) => typeof value === "undefined" ? []
  : Array.isArray(value) ? [...value]
  : [value];

const result1 = intoArray();
    //^? const result1: unknown[]

const result2 = intoArray(undefined);
    //^? const result2: unknown[]

const result3 = intoArray('hello');
    //^? const result3: string[]

const result3Literal = intoArray('hello' as const);
    //^? const result3Literal: "hello"[]

const result4 = intoArray(['hello']);
    //^? const result4: string[]

const result4Literal = intoArray(['hello'] as const);
    //^? const result4Literal: "hello"[]

const result5 = intoArray(['a', 'b']);
    //^? const result5: string[]

const result5Literal = intoArray(['a', 'b'] as const);
    //^? const result5Literal: ("a" | "b")[]


I used a function expression above because that's what you showed in the question, but it's more common to use a declaration with the overload pattern (you'll find this in the overwhelming majority of examples online):

TS Playground

function intoArray (value?: undefined): unknown[];
function intoArray <T extends readonly unknown[]>(value: T): T[number][];
function intoArray <T>(value: T): T[];
function intoArray (value?: unknown) {
  return typeof value === "undefined" ? []
    : Array.isArray(value) ? [...value]
    : [value];
}

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.