How do I cast a JSON Object to a TypeScript class?
Asked Answered
L

29

645

I read a JSON object from a remote REST server. This JSON object has all the properties of a typescript class (by design). How do I cast that received JSON object to a type var?

I don't want to populate a typescript var (ie have a constructor that takes this JSON object). It's large and copying everything across sub-object by sub-object & property by property would take a lot of time.

Update: You can however cast it to a typescript interface!

Libby answered 5/4, 2014 at 1:46 Comment(6)
you can use github.com/vojtechhabarta/typescript-generator to generate TypeScript interfaces in case your JSON is mapped using Java classesFullfledged
I've coded a small casting library: sulphur-blog.azurewebsites.net/typescript-mini-cast-libraryHuron
I've made a tool for this beshanoe.github.io/json2tsDecant
Creating prototype TypeScript class to define your object won't hurt real production code. Take a look at the compiled JS file, all the definitions will be removed, since they are not part of JS.Crissum
I don't consider any of the solutions on StackOverflow to be a comprehensive solution to the problem. So, I created an npm package angular-http-deserializer for this: npmjs.com/package/angular-http-deserializer#usageDjambi
This is a solution: https://mcmap.net/q/37137/-typescript-pass-to-constructor-entire-objectRubefaction
D
226

You can't simple cast a plain-old-JavaScript result from an Ajax request into a prototypical JavaScript/TypeScript class instance. There are a number of techniques for doing it, and generally involve copying data. Unless you create an instance of the class, it won't have any methods or properties. It will remain a simple JavaScript object.

While if you only were dealing with data, you could just do a cast to an interface (as it's purely a compile time structure), this would require that you use a TypeScript class which uses the data instance and performs operations with that data.

Some examples of copying the data:

  1. Copying AJAX JSON object into existing Object
  2. Parse JSON String into a Particular Object Prototype in JavaScript

In essence, you'd just :

var d = new MyRichObject();
d.copyInto(jsonResult);
Demonetize answered 5/4, 2014 at 2:44 Comment(9)
I agree with your answer. As an addition, although I'm not in a place to look it up and test it right now, I think those two steps could be combined by giving a wakeup function as a param to JSON.parse(). Both would still need to be done, but syntactically they could be combined.Kial
Sure, that might work too -- I don't have a sense of whether it would be any more efficient though as it would need to call an extra function call for each property.Demonetize
Definitely not the answer I was looking for :( Out of curiosity why is this? It seems to me the way javascript works that this should be doable.Libby
No, it doesn't work in TypeScript because there isn't a simple way in JavaScript to do this.Demonetize
What about Object.setPrototypeOfKelwin
@Kelwin - I wouldn't recommend it. It's not particularly efficient and not broadly supported.Demonetize
I've just been doing Object.assign(new ApiAuthData(), JSON.parse(rawData))Russom
@Sam, yes, that could work in modern browsers for a simple, flat object without rich typed properties and without read only properties. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…. Using an interface type assertion may be a good choice in some cases.Demonetize
As long as your classes are especially made to receive plain JSON decoded data types they would be essentially wrappers to Objects (with 1 to 1 getters and setters). The objects would be passed as reference to the constructor, so no memory copy, except for some memory address (and the new wrapper object, which won't be much). Totally doable if you control all the classes (I've done it for a private large project, works great). You can control re-serialization back to JSON using the standard .toJSON() method, just set to returned the stored private reference to the original object.Dhumma
A
185

I had the same issue and I have found a library that does the job : https://github.com/pleerock/class-transformer.

It works like this :

let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;

It supports nested children but you have to decorate your class's member.

Anna answered 14/10, 2016 at 11:39 Comment(7)
Oh wow!, this library is not so tiny it have maybe everything you need, even lets you control the transformation with the @transform decorator :DSellma
Take note that this library is barely maintained anymore. It doesn't work with Angular5+ anymore and as they aren't even merging pull requests anymore, I don't think they are going to work on that anytime soon. It's a great library though.Violet
There is a workaround for Angular5+ (it's actually an Angular Bug) : github.com/typestack/class-transformer/issues/108Anna
This works just fine in Angular 6 (at least for my use case which is just to literally map JSON <=> Class)Sharpset
Working with angular8+ and is in active development. For me this is one of the most important utility libraries out thereStralka
Does it copy or just add necessary methods to existing object?Threaten
Works like a charm in Angular 9 (Don't forget to decorate all nested child and import import "reflect-metadata"; in your App.component.ts).Judge
C
75

In TypeScript you can do a type assertion using an interface and generics like so:

var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;

Where ILocationMap describes the shape of your data. The advantage of this method is that your JSON could contain more properties but the shape satisfies the conditions of the interface.

However, this does NOT add class instance methods.

Carce answered 15/8, 2014 at 2:21 Comment(7)
FYI: It's a type assertion, not a cast.Demonetize
See here for the difference between a type assertion and a cast.Syllogize
Where can I find Utilities.JSONLoader?Libido
But it won't have any methods, as mentioned in the answer.Orland
The main point is able to use method(s) which is implemented in the type.Pylle
@StefanHanke looks like the URL changed slightly: "Type Assertion vs. Casting"Cointreau
Is my understanding correct that if the json string contains attributes a,b,c, and the typescript interface has only attributes a and b that this would not throw a casting mismatch? Meaning the different attributes would not cause an issue?Attwood
L
58

If you are using ES6, try this:

class Client{
  name: string

  displayName(){
    console.log(this.name)
  }
}

service.getClientFromAPI().then(clientData => {
  
  // Here the client data from API only have the "name" field
  // If we want to use the Client class methods on this data object we need to:
  let clientWithType = Object.assign(new Client(), clientData)

  clientWithType.displayName()
})

But this method will not work on nested objects, sadly.

Latisha answered 21/12, 2016 at 6:18 Comment(4)
They asked for it in Typescript.Inconsistency
HI @Inconsistency , I mention ES6 because this way the 'Object.assign' method is required.Sempach
In case the default constructor is missing, the target instance can be created through Object.create(MyClass.prototype), bypassing the constructor altogether.Ki
More explanation about nested objects limitation see in #22886495Bodoni
D
37

I found a very interesting article on generic casting of JSON to a Typescript Class:

http://cloudmark.github.io/Json-Mapping/

You end up with following code:

let example = {
                "name": "Mark", 
                "surname": "Galea", 
                "age": 30, 
                "address": {
                  "first-line": "Some where", 
                  "second-line": "Over Here",
                  "city": "In This City"
                }
              };

MapUtils.deserialize(Person, example);  // custom class
Doughy answered 16/6, 2016 at 12:8 Comment(1)
's JSON mapper article link is good read.Oxidimetry
W
33

There is nothing yet to automatically check if the JSON object you received from the server has the expected (read is conform to the) typescript's interface properties. But you can use User-Defined Type Guards

Considering the following interface and a silly json object (it could have been any type):

interface MyInterface {
    key: string;
 }

const json: object = { "key": "value" }

Three possible ways:

A. Type Assertion or simple static cast placed after the variable

const myObject: MyInterface = json as MyInterface;

B. Simple static cast, before the variable and between diamonds

const myObject: MyInterface = <MyInterface>json;

C. Advanced dynamic cast, you check yourself the structure of the object

function isMyInterface(json: any): json is MyInterface {
    // silly condition to consider json as conform for MyInterface
    return typeof json.key === "string";
}

if (isMyInterface(json)) {
    console.log(json.key)
}
else {
        throw new Error(`Expected MyInterface, got '${json}'.`);
}

You can play with this example here

Note that the difficulty here is to write the isMyInterface function. I hope TS will add a decorator sooner or later to export complex typing to the runtime and let the runtime check the object's structure when needed. For now, you could either use a json schema validator which purpose is approximately the same OR this runtime type check function generator

Weatherbound answered 16/1, 2018 at 13:32 Comment(0)
T
30

TLDR: One liner

// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));

The Detailed Answer

I would not recommend the Object.assign approach, as it can inappropriately litter your class instance with irrelevant properties (as well as defined closures) that were not declared within the class itself.

In the class you are trying to deserialize into, I would ensure any properties you want deserialized are defined (null, empty array, etc). By defining your properties with initial values you expose their visibility when trying to iterate class members to assign values to (see deserialize method below).

export class Person {
  public name: string = null;
  public favoriteSites: string[] = [];

  private age: number = null;
  private id: number = null;
  private active: boolean;

  constructor(instanceData?: Person) {
    if (instanceData) {
      this.deserialize(instanceData);
    }
  }

  private deserialize(instanceData: Person) {
    // Note this.active will not be listed in keys since it's declared, but not defined
    const keys = Object.keys(this);

    for (const key of keys) {
      if (instanceData.hasOwnProperty(key)) {
        this[key] = instanceData[key];
      }
    }
  }
}

In the example above, I simply created a deserialize method. In a real world example, I would have it centralized in a reusable base class or service method.

Here is how to utilize this in something like an http resp...

this.http.get(ENDPOINT_URL)
  .map(res => res.json())
  .map((resp: Person) => new Person(resp) ) );

If tslint/ide complains about argument type being incompatible, just cast the argument into the same type using angular brackets <YourClassName>, example:

const person = new Person(<Person> { name: 'John', age: 35, id: 1 });

If you have class members that are of a specific type (aka: instance of another class), then you can have them casted into typed instances through getter/setter methods.

export class Person {
  private _acct: UserAcct = null;
  private _tasks: Task[] = [];

  // ctor & deserialize methods...

  public get acct(): UserAcct {
    return this.acct;
  }
  public set acct(acctData: UserAcct) {
    this._acct = new UserAcct(acctData);
  }

  public get tasks(): Task[] {
    return this._tasks;
  }

  public set tasks(taskData: Task[]) {
    this._tasks = taskData.map(task => new Task(task));
  }
}

The above example will deserialize both acct and the list of tasks into their respective class instances.

This answered 10/4, 2017 at 21:49 Comment(5)
I get this error message: Type '{ name: string, age: number, id: number }' cannot be converted to type 'Person'. Property 'id' is private in type 'Person' but not in type '{ name: string, age: number, id: number }'Chrysalid
How should I use this with enums? Do I have to use the specific type approach and add getter and setter for it?Breda
@TimothyParez When do you set the tasks?Sycosis
I tried to do something similar but my tasks array is empty when i console.log person.Sycosis
For this to compile I had to add an Index Signature to the class: export class Person { [key: string]: any (...) }Bootlick
C
19

Assuming the json has the same properties as your typescript class, you don't have to copy your Json properties to your typescript object. You will just have to construct your Typescript object passing the json data in the constructor.

In your ajax callback, you receive a company:

onReceiveCompany( jsonCompany : any ) 
{
   let newCompany = new Company( jsonCompany );

   // call the methods on your newCompany object ...
}

In in order to to make that work:

1) Add a constructor in your Typescript class that takes the json data as parameter. In that constructor you extend your json object with jQuery, like this: $.extend( this, jsonData). $.extend allows keeping the javascript prototypes while adding the json object's properties.

2) Note you will have to do the same for linked objects. In the case of Employees in the example, you also create a constructor taking the portion of the json data for employees. You call $.map to translate json employees to typescript Employee objects.

export class Company
{
    Employees : Employee[];

    constructor( jsonData: any )
    {
        $.extend( this, jsonData);

        if ( jsonData.Employees )
            this.Employees = $.map( jsonData.Employees , (emp) => {
                return new Employee ( emp );  });
    }
}

export class Employee
{
    name: string;
    salary: number;

    constructor( jsonData: any )
    {
        $.extend( this, jsonData);
    }
}

This is the best solution I found when dealing with Typescript classes and json objects.

Czernowitz answered 8/12, 2016 at 18:33 Comment(5)
I prefer this solution over implementing and maintaining interfaces, because my Angular2 applications have a real application model that may be different to the model of the webservices my application consumes. It can have private datas and calculated properties.Nonunion
Using JQuery in Angular projects is a terrible idea. And if your models contain a bunch of functions on them, they are not models anymore.Jayejaylene
@Jayejaylene You mean POJO, or model? POJO (basically plain objects) have no functions, while model is a wider term, and it includes repository. Repository pattern, in contrast to POJO, is about functions, but it's still model.Jesselyn
@Davor: using JQuery in Angular projects is not a bad idea as long as you do not use it to manipulate the DOM, which is indeed a terrible idea. I do use any library I need for my Angular projects, and for jQuery it is not an option because my project uses SignalR that depends on it. In case of classes, now used by javascript ES6, data is accessed with properties that are function that encapsulate the way the data is stored in memory. For constructors, there is a proper way which using factories.Nonunion
The OP is clearly about plain data models for REST. You're making it needlessly complicated, guys. And yeah, you can use Jquery for additional stuff, but you're importing a massive library to use 1% of it. That is a code smell if I've ever seen one.Jayejaylene
T
17

In my case it works. I used functions Object.assign (target, sources ...). First, the creation of the correct object, then copies the data from json object to the target.Example :

let u:User = new User();
Object.assign(u , jsonUsers);

And a more advanced example of use. An example using the array.

this.someService.getUsers().then((users: User[]) => {
  this.users = [];
  for (let i in users) {
    let u:User = new User();
    Object.assign(u , users[i]);
    this.users[i] = u;
    console.log("user:" + this.users[i].id);
    console.log("user id from function(test it work) :" + this.users[i].getId());
  }

});

export class User {
  id:number;
  name:string;
  fullname:string;
  email:string;

  public getId(){
    return this.id;
  }
}
Tanto answered 10/3, 2017 at 12:10 Comment(5)
What happens when you have a private property?Rapeseed
Because the jsonUser object is not a User class. Without operation Object.assign (u, jsonUsers); You can not use the getId() function. Only after assign you get a valid User object in which you can use the getId() function. The getId() function is only for the example that the operation was successful.Tanto
you can skip the temp var - just do this.users[i] = new User(); Object.assign(this.users[i], users[i])Fortnightly
or even better make use of the return value: this.users[i] = Object.assign(new User(), users[i]);Fortnightly
This long version is for explanation only. You can shorten the code as much as you like :)Tanto
R
9

While it is not casting per se; I have found https://github.com/JohnWhiteTB/TypedJSON to be a useful alternative.

@JsonObject
class Person {
    @JsonMember
    firstName: string;

    @JsonMember
    lastName: string;

    public getFullname() {
        return this.firstName + " " + this.lastName;
    }
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);

person instanceof Person; // true
person.getFullname(); // "John Doe"
Rumilly answered 9/11, 2016 at 7:3 Comment(2)
It not casting, what does it really do?Broncobuster
This solution requires an awful lot of annotations. Is there really no easier way?Ochlophobia
V
6

Personally I find it appalling that typescript does not allow an endpoint definition to specify the type of the object being received. As it appears that this is indeed the case, I would do what I have done with other languages, and that is that I would separate the JSON object from the class definition, and have the class definition use the JSON object as its only data member.

I despise boilerplate code, so for me it is usually a matter of getting to the desired result with the least amount of code while preserving type.

Consider the following JSON object structure definitions - these would be what you would receive at an endpoint, they are structure definitions only, no methods.

interface IAddress {
    street: string;
    city: string;
    state: string;
    zip: string;
}

interface IPerson {
    name: string;
    address: IAddress;
}

If we think of the above in object oriented terms, the above interfaces are not classes because they only define a data structure. A class in OO terms defines data and the code that operates on it.

So we now define a class that specifies data and the code that operates on it...

class Person {
    person: IPerson;

    constructor(person: IPerson) {
        this.person = person;
    }

    // accessors
    getName(): string {
        return person.name;
    }

    getAddress(): IAddress {
        return person.address;
    }

    // You could write a generic getter for any value in person, 
    // no matter how deep, by accepting a variable number of string params

    // methods
    distanceFrom(address: IAddress): float {
        // Calculate distance from the passed address to this persons IAddress
        return 0.0;
    }
}

And now we can simply pass in any object conforming to the IPerson structure and be on our way...

   Person person = new Person({
            name: "persons name",
            address: {
                street: "A street address",
                city: "a city",
                state: "a state",
                zip: "A zipcode"
            }
        });

In the same fashion we can now process the object received at your endpoint with something along the lines of...

Person person = new Person(req.body);    // As in an object received via a POST call

person.distanceFrom({ street: "Some street address", etc.});

This is much more performant and uses half the memory of copying the data, while significantly reducing the amount of boilerplate code you must write for each entity type. It simply relies on the type safety provided by TypeScript.

Viveca answered 2/6, 2020 at 2:11 Comment(6)
The only sideback of this approach is that when you JSON.strigify(person), the result will not be the same as the plain JSON. In other words serialization produces a different output than deserialization.Aluminous
@TiagoStapenhorstMartins But you shouldn't need to stringify the instance of the class. You could just JSON.stringify(person.person) - ie. the person property of the object. (I think, right?)Ogpu
@Ogpu yes, correct! well if you don't mind having the person object nested inside another object this solution is ok.Aluminous
In actual use, if you needed to export the IPerson inside the Person class to JSON - the code performing the export would most likely be found in the Person class, perhaps as a getPersonJSON() method.Viveca
To go one step further, if you understand encapsulation and the value it brings, ideally, no other code would be accessing the IPerson inside Person - only the code in Person should be accessing the IPerson. Everything you might want to do to an IPerson should be done in the Person class - things like save(), delete(), etc.Viveca
Could add a toJSON method to the person class, will be called automatically by stringify that returns this.person. If property used generic name data a base entity class with this method could be extendedSarad
R
5

Use a class extended from an interface.

Then:

    Object.assign(
        new ToWhat(),
        what
    )

And best:

    Object.assign(
        new ToWhat(),
        <IDataInterface>what
    )

ToWhat becomes a controller of DataInterface

Russom answered 6/11, 2019 at 21:37 Comment(0)
C
5

If you need to cast your json object to a typescript class and have its instance methods available on the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:

Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)
Cerracchio answered 10/1, 2020 at 19:58 Comment(1)
MDN says that setPrototypeOf is very slow and not really recommended, see developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Smyth
K
4

An old question with mostly correct, but not very efficient answers. This what I propose:

Create a base class that contains init() method and static cast methods (for a single object and an array). The static methods could be anywhere; the version with the base class and init() allows easy extensions afterwards.

export class ContentItem {
    // parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
    static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
        // if we already have the correct class skip the cast
        if (doc instanceof proto) { return doc; }
        // create a new object (create), and copy over all properties (assign)
        const d: T = Object.create(proto.prototype);
        Object.assign(d, doc);
        // reason to extend the base class - we want to be able to call init() after cast
        d.init(); 
        return d;
    }
    // another method casts an array
    static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
        return docs.map(d => ContentItem.castAs(d, proto));
    }
    init() { }
}

Similar mechanics (with assign()) have been mentioned in @Adam111p post. Just another (more complete) way to do it. @Timothy Perez is critical of assign(), but imho it is fully appropriate here.

Implement a derived (the real) class:

import { ContentItem } from './content-item';

export class SubjectArea extends ContentItem {
    id: number;
    title: string;
    areas: SubjectArea[]; // contains embedded objects
    depth: number;

    // method will be unavailable unless we use cast
    lead(): string {
        return '. '.repeat(this.depth);
    }

    // in case we have embedded objects, call cast on them here
    init() {
        if (this.areas) {
            this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
        }
    }
}

Now we can cast an object retrieved from service:

const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);

All hierarchy of SubjectArea objects will have correct class.

A use case/example; create an Angular service (abstract base class again):

export abstract class BaseService<T extends ContentItem> {
  BASE_URL = 'http://host:port/';
  protected abstract http: Http;
  abstract path: string;
  abstract subClass: typeof ContentItem;

  cast(source: T): T {
    return ContentItem.castAs(source, this.subClass);
  }
  castAll(source: T[]): T[] {
    return ContentItem.castAllAs(source, this.subClass);
  }

  constructor() { }

  get(): Promise<T[]> {
    const value = this.http.get(`${this.BASE_URL}${this.path}`)
      .toPromise()
      .then(response => {
        const items: T[] = this.castAll(response.json());
        return items;
      });
    return value;
  }
}

The usage becomes very simple; create an Area service:

@Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
  path = 'area';
  subClass = SubjectArea;

  constructor(protected http: Http) { super(); }
}

get() method of the service will return a Promise of an array already cast as SubjectArea objects (whole hierarchy)

Now say, we have another class:

export class OtherItem extends ContentItem {...}

Creating a service that retrieves data and casts to the correct class is as simple as:

@Injectable()
export class OtherItemService extends BaseService<OtherItem> {
  path = 'other';
  subClass = OtherItem;

  constructor(protected http: Http) { super(); }
}
Keystroke answered 19/12, 2017 at 9:27 Comment(0)
O
4

Use 'as' declaration:

const data = JSON.parse(response.data) as MyClass;
Offend answered 11/6, 2020 at 16:35 Comment(4)
This technique was mentioned in this answer from two years prior, and as has been discussed elsewhere, does not add any functions that may be declared on MyClass.Biannual
I however think this is very efficient to use this for Model objects which generally do not have functions.Error
The only reason you would do this is in preparation to perform processing on the resulting object - indicating a need for a method of converting that provides the functions as well. See my answer above, which provides the equivalent of the above and the functions.Viveca
This does not work for empty object. Such parsing will return plain ObjectFording
T
4

There are several ways to do it, lets examine a some options:

class Person {
   id: number | undefined;
   firstName: string | undefined;
   //? mark for note not required attribute.
   lastName?: string;
}

// Option 1: Fill any attribute and it would be accepted.
const person1= { firstName: 'Cassio' } as Person ;
console.log(person1);

// Option 2. All attributes must assign data.
const  person2: Person = { id: 1, firstName: 'Cassio', lastName:'Seffrin' };  
console.log(person2);

//  Option 3. Use partial interface if all attribute not required.
const  person3: Partial<Person> = { firstName: 'Cassio' };  
console.log(person3);

//  Option 4. As lastName is optional it will work
const  person4: Person = { id:2, firstName: 'Cassio'  };
console.log(person4);

//  Option 5. Fill any attribute and it would be accepted.
const person5 = <Person> {firstName: 'Cassio'}; 
console.log(person5 );

Result:

[LOG]: {
  "firstName": "Cassio"
} 
[LOG]: {
  "id": 1,
  "firstName": "Cassio",
  "lastName": "Seffrin"
} 
[LOG]: {
  "firstName": "Cassio"
} 
[LOG]: {
  "id": 2,
  "firstName": "Cassio"
} 
[LOG]: {
  "firstName": "Cassio"
} 

It will also work if you have an interface instead a Typescript class.

interface PersonInterface {
   id: number;
   firstName: string;
   lastName?: string;
}

Play this code

Transmittance answered 18/12, 2021 at 1:41 Comment(0)
M
3

You can create an interface of your type (SomeType) and cast the object in that.

const typedObject: SomeType = <SomeType> responseObject;
Milter answered 16/5, 2019 at 19:42 Comment(0)
C
2

FOR JAVA LOVERS

Make POJO class

export default class TransactionDTO{
    constructor() {
    }
}

create empty object by class

let dto = new TransactionDto()   // ts object
let json = {name:"Kamal",age:40} // js object

let transaction: TransactionDto = Object.assign(dto,JSON.parse(JSON.stringify(json)));//conversion
Callas answered 1/3, 2022 at 13:14 Comment(0)
K
1

https://jvilk.com/MakeTypes/

you can use this site to generate a proxy for you. it generates a class and can parse and validate your input JSON object.

Kitchener answered 13/2, 2020 at 13:4 Comment(0)
D
0

I used this library here: https://github.com/pleerock/class-transformer

<script lang="ts">
    import { plainToClass } from 'class-transformer';
</script>

Implementation:

private async getClassTypeValue() {
  const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}

Sometimes you will have to parse the JSON values for plainToClass to understand that it is a JSON formatted data

Discommon answered 5/7, 2018 at 11:38 Comment(1)
The 'class-transformer' library is already suggested in other answer above https://mcmap.net/q/36996/-how-do-i-cast-a-json-object-to-a-typescript-classBodoni
R
0

In the lates TS you can do like this:

const isMyInterface = (val: any): val is MyInterface => {
  if (!val) { return false; }
  if (!val.myProp) { return false; }
  return true;
};

And than user like this:

if (isMyInterface(data)) {
 // now data will be type of MyInterface
}
Reentry answered 26/1, 2019 at 13:29 Comment(0)
P
0

I ran into a similar need. I wanted something that will give me easy transformation from/to JSON that is coming from a REST api call to/from specific class definition. The solutions that I've found were insufficient or meant to rewrite my classes' code and adding annotations or similars.

I wanted something like GSON is used in Java to serialize/deserialize classes to/from JSON objects.

Combined with a later need, that the converter will function in JS as well, I ended writing my own package.

It has though, a little bit of overhead. But when started it is very convenient in adding and editing.

You initialize the module with :

  1. conversion schema - allowing to map between fields and determine how the conversion will be done
  2. Classes map array
  3. Conversion functions map - for special conversions.

Then in your code, you use the initialized module like :

const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');

const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');

or , to JSON :

const jsonObject = this.converter.convertToJson(myClassInstance);

Use this link to the npm package and also a detailed explanation to how to work with the module: json-class-converter

Also wrapped it for
Angular use in : angular-json-class-converter

Paramatta answered 21/7, 2019 at 17:27 Comment(0)
S
0

Pass the object as is to the class constructor; No conventions or checks

interface iPerson {
   name: string;
   age: number;
}

class Person {
   constructor(private person: iPerson) { }

   toString(): string {
      return this.person.name + ' is ' + this.person.age;
   }  
}


// runs this as // 
const object1 = { name: 'Watson1', age: 64 };
const object2 = { name: 'Watson2' };            // age is missing

const person1 = new Person(object1);
const person2 = new Person(object2 as iPerson); // now matches constructor

console.log(person1.toString())  // Watson1 is 64
console.log(person2.toString())  // Watson2 is undefined
Sabbat answered 31/8, 2019 at 5:46 Comment(0)
P
0

You can use this npm package. https://www.npmjs.com/package/class-converter

It is easy to use, for example:

class UserModel {
  @property('i')
  id: number;

  @property('n')
  name: string;
}

const userRaw = {
  i: 1234,
  n: 'name',
};

// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
// const userModel = {
//   id: 1234,
//   name: 'name',
// }
Payee answered 11/12, 2019 at 7:31 Comment(0)
M
0

You can with a single tapi.js! It's a lightweight automapper that works in both ways.

npm i -D tapi.js

Then you can simply do

let typedObject = new YourClass().fromJSON(jsonData)

or with promises

axios.get(...).as(YourClass).then(typedObject => { ... })

You can read more about it on the docs.

Millsaps answered 3/11, 2021 at 13:22 Comment(0)
F
0

I think that json2typescript is a good alternative https://www.npmjs.com/package/json2typescript

You can convert json to Class model with a simple model class with annotations

Used in project

Funk answered 14/11, 2022 at 23:50 Comment(0)
E
0

You can do something like this:

// Map method
function mapJSONtoObj<Type>(json: any, arg: Type): Type {
  Object.keys(json).forEach((elem:string)=>{
    let iterable:any = arg;
    let markerType = 0

    // we need to work around the fact that null and array are both objects
    markerType |= typeof(json[elem]) == 'object'?1:0
    markerType |= Array.isArray(json[elem])?2:0
    markerType |= json[elem]==null?4:0

    switch(markerType)
    {
      case 1:
      mapJSONtoObj(json[elem], iterable[elem])
      break;
      default:
      iterable[elem] = json[elem];
    }
  })
  return arg;
}

// Using example

class Internal {
  internVar: number = 0
}
class InternalArr {
  internArrVar: number = 0
}
class Root {
  rNum: number = 0
  internArrOfObj: InternalArr[] = []
  internObj: Internal = new Internal()
} 

let json = {
  rNum:1,
  internArrOfObj: [{
    internArrVar: 2
  }],
  internObj: {
    internVar:3
  }
}

let at = new Root()
let to = mapJSONtoObj(json, at);

And, of course, you can rewrite map classes to interfaces and use it as:

let at = new Object as Root // if Root is interface

// And, finally, in "case 1" we should handle the condition that the object is undefined

// your mapJSONtoObj function
...
 switch(markerType)
    {
      case 1:
      iterable[elem] = {} // <---- add this one
      mapJSONtoObj(json[elem], iterable[elem])
      break;
      default:
      iterable[elem] = json[elem];
    }
...
Edmead answered 22/8, 2023 at 16:22 Comment(0)
B
-1

You can cast json to property like this

class Jobs {
  constructor(JSONdata) {
    this.HEAT = JSONdata.HEAT;    
    this.HEAT_EAF = JSONdata.HEAT_EAF;    
  }
  
}

 var job = new Jobs({HEAT:'123',HEAT_EAF:'456'});
Biotype answered 3/10, 2020 at 8:15 Comment(0)
A
-2

This is a simple and a really good option

let person = "{"name":"Sam","Age":"30"}";

const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
let objectConverted = JSON.parse(textValue, jsonParse);

And then you'll have

objectConverted.name
Antiperspirant answered 19/2, 2018 at 17:16 Comment(1)
Does this really answer the question of casting to a TypeScript class? What are all these undefineds, anys and pipes? No harm if they're necessary but what are they doing? Or is this something that 'just worked' for the author and they know not why?Waisted

© 2022 - 2024 — McMap. All rights reserved.