97amarnathk
97amarnathk

Reputation: 1045

Typescript : convert array union of ObjectId or string to an array of string

I have an array foo of the type:

foo: ObjectId[] | string[];

I need to assign it to a string array bar as follows :

let bar : string[] = foo.map( (e) => e.toString())

This should be correct since both ObjectID and string types have toString() method defined. However the above code is not compiled because of the following error :

This expression is not callable.
  Each member of the union type '(<U>(callbackfn: (value: ObjectId, index: number, array: ObjectId[]) => U, thisArg?: any) => U[]) | (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other.

I am currently using the following workaround :

let bar : string[] = (foo as Array<string|ObjectID>).map( (e) => e.toString())

Is there some better way to cast the array of mixed type to string?

Upvotes: 4

Views: 375

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42228

Allowing foo.map()

It will work if you define foo with a signature that says "it's an array of strings and ObjectIds, and also it's an array of all ObjectIds or all strings". This feels silly because the first part is implied by the second part, but typescript doesn't work that way.

foo: (ObjectId | string)[] & (ObjectId[] | string[]);

This is better than casting as (ObjectId | string)[] because we don't lose the information that the array has to be all of one type. But having the (ObjectId | string)[] included as part of the type means that it's ok to map.

You can make this into a utility type:

type ArrayOfEither<A, B> = (A | B)[] & (A[] | B[]);

Mapping foo Through a Function

If you can't change the type of foo, then you can do your mapping in a separate function which takes foo as an argument.

This works because the original signature ObjectId[] | string[] is very specific, but it's assignable to broader types like ArrayOfEither<ObjectId[], string[]> or just Stringable[].

interface Stringable {
    toString(): string;
}

const myFunction = ( array: Stringable[] ): string[] => {
    return array.map(e => e.toString());
}

declare const foo: ObjectId[] | string[];

myFunction(foo); // no errors

Playground Link

Upvotes: 1

Related Questions