75

I am currently trying to convert my received JSON Object into a TypeScript class with the same attributes and I cannot get it to work. What am I doing wrong?

Employee Class

export class Employee{
    firstname: string;
    lastname: string;
    birthdate: Date;
    maxWorkHours: number;
    department: string;
    permissions: string;
    typeOfEmployee: string;
    note: string;
    lastUpdate: Date;
}

Employee String

{
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": <anynumber>,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
    //I will add note later
}

My Attempt

let e: Employee = new Employee();

Object.assign(e, {
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
});

console.log(e);

Link to Typescript Playground

4
  • What's not working exactly? It's not compiling? If so, what's the error? Commented Nov 4, 2016 at 11:15
  • Edited my question. It works now but the object is not recognized as Employee, only as Object. Commented Nov 4, 2016 at 11:52
  • Check this gist and try it on the playground. The employee variable has both properties available. Commented Nov 4, 2016 at 12:46
  • Possible duplicate of How do I cast a JSON object to a typescript class Commented Mar 14, 2018 at 20:52

12 Answers 12

75

If you use a TypeScript interface instead of a class, things are simpler:

export interface Employee {
    typeOfEmployee_id: number;
    department_id: number;
    permissions_id: number;
    maxWorkHours: number;
    employee_id: number;
    firstname: string;
    lastname: string;
    username: string;
    birthdate: Date;
    lastUpdate: Date;
}

let jsonObj = JSON.parse(employeeString); // string to "any" object first
let employee = jsonObj as Employee;

If you want a class, however, simple casting won't work. For example:

class Foo {
    name: string;
    public pump() { }
}

let jsonObj = JSON.parse('{ "name":"hello" }');
let fObj = jsonObj as Foo;
fObj.pump(); // crash, method is undefined!

For a class, you'll have to write a constructor which accepts a JSON string/object and then iterate through the properties to assign each member manually, like this:

class Foo {
    name: string;

    constructor(jsonStr: string) {
        let jsonObj = JSON.parse(jsonStr);
        for (let prop in jsonObj) {
            this[prop] = jsonObj[prop];
        }
    }
}

let fObj = new Foo(theJsonString);
Sign up to request clarification or add additional context in comments.

8 Comments

This seems logical to me. I just saw that my JSON Converter at the source sends the id's because the employee class there has got them. But it should send the values which the id's point at. I will adjust that and than try to cast it like in your example.
I adjusted the json string and updated my question above. I also implemented your solution but it is still not recognized as Employee and throws a type mismatch error.
@moessi774 I just updated me answer, I guess I exactly understood your question now.
I did not know about interfaces for now. So if I just use my class for typisation a interface would be much smarter. Thank you for your answer.
This good answer is from 2016. With ES6 you can use Object.assign(this, input) inside a function in your object to avoid manually iterating the properties. You still need to take care of object nesting manually, though.
|
45

The reason that the compiler lets you cast the object returned from JSON.parse to a class is because typescript is based on structural subtyping.
You don't really have an instance of an Employee, you have an object (as you see in the console) which has the same properties.

A simpler example:

class A {
    constructor(public str: string, public num: number) {}
}

function logA(a: A) {
    console.log(`A instance with str: "${ a.str }" and num: ${ a.num }`);
}

let a1 = { str: "string", num: 0, boo: true };
let a2 = new A("stirng", 0);
logA(a1); // no errors
logA(a2);

(code in playground)

There's no error because a1 satisfies type A because it has all of its properties, and the logA function can be called with no runtime errors even if what it receives isn't an instance of A as long as it has the same properties.

That works great when your classes are simple data objects and have no methods, but once you introduce methods then things tend to break:

class A {
    constructor(public str: string, public num: number) { }

    multiplyBy(x: number): number {
        return this.num * x;
    }
}

// this won't compile:
let a1 = { str: "string", num: 0, boo: true } as A; // Error: Type '{ str: string; num: number; boo: boolean; }' cannot be converted to type 'A'

// but this will:
let a2 = { str: "string", num: 0 } as A;

// and then you get a runtime error:
a2.multiplyBy(4); // Error: Uncaught TypeError: a2.multiplyBy is not a function

(code in playground)


Edit

This works just fine:

const employeeString = '{"department":"<anystring>","typeOfEmployee":"<anystring>","firstname":"<anystring>","lastname":"<anystring>","birthdate":"<anydate>","maxWorkHours":0,"username":"<anystring>","permissions":"<anystring>","lastUpdate":"<anydate>"}';
let employee1 = JSON.parse(employeeString);
console.log(employee1);

(code in playground)

If you're trying to use JSON.parse on your object when it's not a string:

let e = {
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
}
let employee2 = JSON.parse(e);

Then you'll get the error because it's not a string, it's an object, and if you already have it in this form then there's no need to use JSON.parse.

But, as I wrote, if you're going with this way then you won't have an instance of the class, just an object that has the same properties as the class members.

If you want an instance then:

let e = new Employee();
Object.assign(e, {
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
});

17 Comments

So i basically can cast any object string to an object or interface if it has the same (but it must not have all) properties as the object or interface? And this works for both. But if I need methods I have to use a class instead of an interface and than I can only call the methods of my classes objects is they were made through the constructor of the class. Right? And if can change my employee class to an interface rightaway because I just need it for typisation?
But can I also cast an object string to an object or interface if the string has more properties than the object? Because in my case this is not working.
Check my revised answer
I can't understand what you mean. Please update your question with this info and explain what you did and what you got back
Using your exact code I get: Employee {department: "<anystring>", typeOfEmployee: "<anystring>", firstname: "<anystring>", lastname: "<anystring>", birthdate: "<anydate>"…} which is fine
|
7
let employee = <Employee>JSON.parse(employeeString);

Remember: Strong typings is compile time only since javascript doesn't support it.

1 Comment

What's the difference between your example and his?
6

Your JSON data may have some properties that you do not have in your class. For mapping You can do simple custom mapping

export class Employe{ ////
    static parse(json: string) {
           var data = JSON.parse(json);
            return new Employe(data.typeOfEmployee_id, data.firstName.. and others);
       }
}

and also specifying constructor in your Employee class.

1 Comment

This looks like a good solution. I try it some more times with my current way. If it is still not working I implement yours.
3

i like to use a littly tiny library called class-transformer.

it can handle nested-objects, map strings to date-objects and handle different json-property-names a lot more.

Maybe worth a look.

import { Type, plainToClass, Expose } from "class-transformer";
import 'reflect-metadata';

export class Employee{
    @Expose({ name: "uid" })
    id: number;

    firstname: string;
    lastname: string;
    birthdate: Date;
    maxWorkHours: number;
    department: string;

    @Type(() => Permission)
    permissions: Permission[] = [];
    typeOfEmployee: string;
    note: string;

    @Type(() => Date)
    lastUpdate: Date;
}

export class Permission {
  type : string;
}

let json:string = {
    "uid": 123,
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 1,
    "username": "<anystring>",
    "permissions": [
      {'type' : 'read'},
      {'type' : 'write'}
    ],
    "lastUpdate": "2020-05-08"
}

console.log(plainToClass(Employee, json));

```

1 Comment

Agreed. For non-trivial cases with nested object hierarchies, class-transformer is a useful tool that greatly reduces the amount of manual deserialization code required for an app that sends/receives external data in JSON format.
2

First of all you need to be sure that all attributes of that comes from the service are named the same in your class. Then you can parse the object and after that assign it to your new variable, something like this:

const parsedJSON = JSON.parse(serverResponse);
const employeeObj: Employee = parsedJSON as Employee;

Try that!

1 Comment

This solution has one big problem - not support to nested object
1

You can cast the the json as follows:

Given your class:

export class Employee{
    firstname: string= '';
}

and the json:

let jsonObj = {
    "firstname": "Hesham"
};

You can cast it as follows:

let e: Employee = jsonObj as Employee;

And the output of console.log(e); is:

{ firstname: 'Hesham' }

Comments

1

you can make a new object of your class and then assign it's parameters dynamically from the JSON object's parameters.

const employeeData = JSON.parse(employeeString);
let emp:Employee=new Employee();
const keys=Object.keys(employeeData);
keys.forEach(key=>{
    emp[key]=employeeData[key];
});
console.log(emp);

now the emp is an object of Employee containing all fields of employeeString's Json object(employeeData);

Comments

0

Try to use constructor procedure in your class.

Object.assign

is a key

Please take a look on this sample:

class Employee{
    firstname: string;
    lastname: string;
    birthdate: Date;
    maxWorkHours: number;
    department: string;
    permissions: string;
    typeOfEmployee: string;
    note: string;
    lastUpdate: Date;

    constructor(original: Object) { 
        Object.assign(this, original);
    }
}

let e = new Employee({
    "department": "<anystring>",
    "typeOfEmployee": "<anystring>",
    "firstname": "<anystring>",
    "lastname": "<anystring>",
    "birthdate": "<anydate>",
    "maxWorkHours": 3,
    "username": "<anystring>",
    "permissions": "<anystring>",
    "lastUpdate": "<anydate>"
});
console.log(e);

Comments

0

You can perform this operation using syntax "as"?

async getProfile(): Promise<Contact> {
      const url: string = this.baseApi;
    
      const response = await this.http.get(url).toPromise()
      return JSON.parse(response.json()) as Contact;
    }

Comments

0

Here's a concise solution that works well for simple "flat" objects:

let listOfObjectsWithMethods = listFromBackend.map( o => Object.assign(new MyType(), o));

Once you perform this transformation, you will be able to access the methods of objects declared in the MyType class

Comments

-3
if it is coming from server as object you can do 

this.service.subscribe(data:any) keep any type on data it will solve the issue

2 Comments

'Any' type is variable, when I get a wrong object, it may be necessary to do a lot of if checks below. The object may not be parsed correctly or there may be a missing property.
You should use json parse instead. Json.parse returns any type, but can be converted to an object using the 'as'syntax.

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.