0

Not A Duplicate

My question had nothing to do with trying to find the type nor the class name of an object. My question was about "casting" an object to a type -- which stemmed from a misunderstanding of TypeScript and how it worked. It also related to the use of Object spread while casting.

Original Question

I want to use object spread to map data from the server into a class defined on my client. I was trying to get away from having to pass data to a constructor and map each property with a loop as illustrated here. I tried the following:

//Data from server
var data = [
   {id: 1, name: "Book 1", authorName: "Author 1", 
     steps: [
       {id: 1, name: "Step 1"},
       {id: 2, name: "Step 2"}
     ]},
   {id: 2, name: "Book 2", authorName: "Author 2",
     steps: [
       {id: 1, name: "Step 1"},
       {id: 3, name: "Step 3"}
     ]}
 ];

 interface IBook {
    id: number;
    name: string;
    authorName: string;
    steps:IStep[];
 }

 interface IStep {
    id: number;
    name: string;
    status: string;
    processing: boolean;
 }

 class Book implements IBook {
   id: number;
   name: string;
   authorName: string;
   steps : Step[] ;
 }

 class Step implements IStep {
    id: number;
    name: string;
    status: string = "unknown";
    processed: boolean = false;
 }

 var list : Book[] = data.map(bitem=> {
      var book = <Book>{ ...bitem, ...new Book() } as Book;
      console.log(book) //Shows 'object' not 'Book'
      var steps = bitem.steps.map(sitem => <Step>{ ...sitem, ...new Step() } as Step;);]
      book.steps = steps;
      return book;
 }

 console.log(typeof list[0]); //Expect 'Book' but get 'object'

I'm curious why the cast to type Book yields an object instead? Is there an easy way to accomplish this or do I need to use the constructor method to accomplish this kind of mapping?

RHarris
  • 10,641
  • 13
  • 59
  • 103
  • [`typeof` will never return `Book`](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/typeof), it can only ever return `object` in cases like this – Joachim Sauer Jun 12 '19 at 15:44
  • 1
    There are no types at run time. typescript does not exist at runtime, only javascript does and JavaScript has no concept of user defined types, everything that starts `{}` is an object. – Avin Kavish Jun 12 '19 at 15:54
  • 1
    Typescript is compiled to JavaScript. Its benefits are almost mostly at compile-time. Turn off sourcemaps and see the JavaScript that is actually running. You'll realise why the output is the way it is. – maazadeeb Jun 12 '19 at 15:57
  • 1
    You need the constructor, yes. What you're calling a "cast" is actually a [type assertion](https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions) which has no runtime effect. – jcalz Jun 12 '19 at 15:58
  • If one of you wants to summarize these comments, I'll accept it as the answer. – RHarris Jun 12 '19 at 17:32
  • Possible duplicate of [Get an object's class name at runtime](https://stackoverflow.com/questions/13613524/get-an-objects-class-name-at-runtime) – Heretic Monkey Jun 12 '19 at 19:12
  • There are types at runtime, it's the prototype of your object. Not all types in TypeScript map to what JavaScript considers a type. If you had an actual `class Book` and called `new Book()` your console.log would say that it's a Book. However, you are just creating a raw object that matches the interface because TS is duck typed – Ruan Mendes Jun 12 '19 at 19:13
  • Side note and pet peeve: A map function without a `return` means you're doing it wrong. The map iterator should not have side effects. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Description `Since map builds a new array, using it when you aren't using the returned array is an anti-pattern; use forEach or for-of instead. Signs you shouldn't be using map: A) You're not using the array it returns, and/or B) You're not returning a value from the callback.` – Ruan Mendes Jun 12 '19 at 19:15
  • @JuanMendes, thanks for the catch. I had caught that in my test code after posting this thread but forgot to update my code here on SO. – RHarris Jun 12 '19 at 20:03

1 Answers1

0

What about this code, you can pass an object to the constructor and create an instance of the same by below technique

 class Book implements IBook {
   id: number;
   name: string;
   authorName: string;
   steps : Step[] ;
   constructor(params: IBook) {
     Object.assign(this, params);
   }
 }

 class Step implements IStep {
    id: number;
    name: string;
    status: string = "unknown";
    processing: boolean = false;
    constructor(params: IStep) {
      Object.assign(this, params);
    }
 }


var list : Book[] = data.map((bitem:any)=> {
      var book = new Book(bitem);
      console.log(book) //Shows 'object' not 'Book'
      var steps = bitem.steps.map(sitem => (new Step(sitem)));
      book.steps = steps;
      retrun book;
 });

working sample here

Reza
  • 18,865
  • 13
  • 88
  • 163
  • This is actually almost a good solution, but it's not complete, you are not ignoring the new book being created; without an explanation, it's not very useful to those who don't already know the answer – Ruan Mendes Jun 12 '19 at 19:18