How to perform runtime-style casting in TypeScript?

I'm currently working on a typescript project, and I really like the type inference that typescript leads to the table. Nevertheless, when receiving objects from HTTP calls, I can attribute them to the desired type, get code completion and functions to call compilation time on them, but this leads to runtime errors

Example:

class Person{ name: string; public giveName() { return this.name; } constructor(json: any) { this.name = json.name; } } var somejson = { 'name' : 'John' }; // Typically from AJAX call var john = <Person>(somejson); // The cast console.log(john.name); // 'John' console.log(john.giveName()); // 'undefined is not a function' 

Although this compiles nicely - and intellisense offers me to use this function, it gives an exception at runtime. The solution for this could be:

 var somejson = { 'name' : 'Ann' }; var ann = new Person(somejson); console.log(ann.name); // 'Ann' console.log(ann.giveName()); // 'Ann' 

But this will require me to create constructors for all my types. In paticular, dealing with tree types and / or collections that are part of the AJAX call, you would need to iterate over all the elements and a new instance for each.

So my question is: is there a more elegant way to do this? That is, casting to and having prototypical functions for it immediately?

+19
typescript
Aug 23 '15 at 14:14
source share
3 answers

Take a look at the compiled JavaScript and you will see that the casting statement disappears because it is only for compilation. Right now you are telling the compiler that somejson is of type Person . The compiler believes you, but in this case it is not.

Therefore, this issue is a JavaScript issue at runtime.

The main goal to get this to work is to somehow tell JavaScript what the relationship between the classes is. So that...

  • Find a way to describe the relationships between classes.
  • Create something to automatically map json to classes based on this relationship data.

There are many ways to solve this, but I will give one example from the head. This should help describe what needs to be done.

Let's say we have this class:

 class Person { name: string; child: Person; public giveName() { return this.name; } } 

And this json data:

 { name: 'John', child: { name: 'Sarah', child: { name: 'Jacob' } } } 

To display this automatically as instances of Person , we need to tell JavaScript how these types are related. We cannot use TypeScript-type information because we will lose it after compiling it. One way to do this is to have a static property in the type that describes it. For example:

 class Person { static relationships = { child: Person }; name: string; child: Person; public giveName() { return this.name; } } 

Then, here is an example of a reusable function that handles creating objects for us based on these relationships:

 function createInstanceFromJson<T>(objType: { new(): T; }, json: any) { const newObj = new objType(); const relationships = objType["relationships"] || {}; for (const prop in json) { if (json.hasOwnProperty(prop)) { if (newObj[prop] == null) { if (relationships[prop] == null) { newObj[prop] = json[prop]; } else { newObj[prop] = createInstanceFromJson(relationships[prop], json[prop]); } } else { console.warn(`Property ${prop} not set because it already existed on the object.`); } } } return newObj; } 

Now the following code will work:

 const someJson = { name: 'John', child: { name: 'Sarah', child: { name: 'Jacob' } } }; const person = createInstanceFromJson(Person, someJson); console.log(person.giveName()); // John console.log(person.child.giveName()); // Sarah console.log(person.child.child.giveName()); // Jacob 

Playground

Ideally, the best way is to use something that really reads TypeScript code and creates an object that maintains class relationships. Thus, we do not need to manually maintain relationships and worry about code changes. For example, right now, refactoring code is a little risky with this setting. I'm not sure that at the moment there is something similar, but it is definitely possible.

Alternative solution

I just realized that I had already answered a similar question with a slightly different solution (although this is not related to the embedded data). You can read it here for some ideas:

JSON class instance for TypeScript?

+6
Aug 23 '15 at 17:08
source share

A class prototype can dynamically affect an object:

 function cast<T>(obj: any, cl: { new(...args): T }): T { obj.__proto__ = cl.prototype; return obj; } var john = cast(/* somejson */, Person); 

See __proto__ documentation here .

+7
Aug 24 '15 at 15:41
source share

You can use Object.assign, for example:

 var somejson = { 'name' : 'Ann' }; var ann = Object.assign(new Person, somejson); console.log(ann.name); // 'Ann' console.log(ann.giveName()); // 'Ann' 

But if you have nested classes, you must display the throw object and assign it to each element.

0
Jul 09 '19 at 9:47
source share



All Articles