rst
rst

Reputation: 2734

Map array of object to another array with different object in typescript

If i have something like

array1: string[] = ['foo', 'bar'];

and an interface like

export interface MyObject { name: string; }

how can I map array1 to another array of type MyObject?

array2 : MyObject[];
array2 = array1.map(s => ...);

I thought about something like

array2 = array1.map<MyObject>(s => new MyObject(...));

Upvotes: 30

Views: 74286

Answers (2)

jcalz
jcalz

Reputation: 330571

This is usually where I digress into a long lecture about the difference between types and values in TypeScript, but here I'll try to make it short: since MyObject is only known to be a type and not a constructor value, you can't call new MyObject(). Instead, if you want to make a value of type MyObject, you can just use a plain old object of the right shape, such as via object literal notation:

const myObject: MyObject = {name: "Alice"}; // no error

So your map function can be the following:

const array2: MyObject[] = array1.map(s => ({name: s})); // no error

Simple, right?

Uh, well, please note as in the comments above that the parentheses around {name: s} are necessary, because for better or worse, JavaScript parsers interpret arrow functions of the form (x => {...}) as having a block body where the stuff inside the curly braces are statements. And when you interpret name: s as a statement it means that name is a label, and s is just a string expression statement with the optional semicolon omitted. Interpreted as a function body, {name: s} just evaluates s and doesn't return a defined value (it is void, which is essentially the same as undefined), which is why you get the following weird error if you leave out the parentheses:

let array2: MyObject[] = array1.map(s => { name: s }); // error!
// Type 'void[]' is not assignable to type 'MyObject[]'.
// at runtime, array2 will be [undefined, undefined] which is not what you wanted 🙁

JavaScript parsing is a bit tricky this way. Adding the parentheses to make s => (...) fixes it. Now the stuff after the arrow can only be interpreted as a concise body... that is, a single expression. And interpreting ({name: s}) as an expression yields the object literal that we wanted.

Yay, I avoided writing a book about types and values and somehow ended up writing a different book about expressions and statements. Oh well.

Upvotes: 58

Artyom Amiryan
Artyom Amiryan

Reputation: 2976

you can't call MyObject as constructor because it is interface and not class. So an option here you can just map array1 properties as MyObject properties and after that change type to MyObject[] array with as

const array1: string[] = ['foo', 'bar'];

export interface MyObject { name: string; }

let array2 : MyObject[];

array2 = array1.map(v => { return {name: v}; }) as MyObject[];

or another option, you can also create MyObject class which will implement IMyObject interface and you can call constructor. Here is an example

const array1: string[] = ['foo', 'bar'];

export interface IMyObject { name: string; }

class MyObject implements IMyObject {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

let array2 : IMyObject[];

array2 = array1.map(v => new MyObject(v)) as IMyObject[];

Upvotes: 12

Related Questions