4

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?

6
  • Playground typescriptlang.org/play?#code/… Commented Oct 11, 2020 at 15:14
  • 1
    @Terry this has nothing to do with ObjectID type. It has toString Method. The issue is with the union. Typescript afaik doesnt allow me to map over a union. Ref. stackoverflow.com/questions/49510832/… Commented Oct 11, 2020 at 15:15
  • 1
    Does this answer your question? Typescript: How to map over union array type? Commented Oct 11, 2020 at 15:21
  • 1
    Casting the array as Array<String| ObjectID> seems as a workaround to me. Ideally I would want an error if the array is not entirely of strings or not entirely of ObjectIDs Commented Oct 11, 2020 at 15:21
  • 1
    ^Alright, I see your concern. The work around isn't particularly nice. But you do still have type enforcement on the array before you cast it, so it shouldn't be a problem. Commented Oct 11, 2020 at 15:26

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

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.