67

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

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
moessi774
  • 1,029
  • 2
  • 12
  • 19
  • What's not working exactly? It's not compiling? If so, what's the error? – goenning Nov 04 '16 at 11:15
  • Edited my question. It works now but the object is not recognized as Employee, only as Object. – moessi774 Nov 04 '16 at 11:52
  • Check this [gist](https://gist.github.com/goenning/a4caf5f31c22716dcdbd0e5814313502) and try it on the [playground](http://www.typescriptlang.org/play). The `employee` variable has both properties available. – goenning Nov 04 '16 at 12:46
  • Possible duplicate of [How do I cast a JSON object to a typescript class](https://stackoverflow.com/questions/22875636/how-do-i-cast-a-json-object-to-a-typescript-class) – Franklin Yu Mar 14 '18 at 20:52

12 Answers12

67

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);
rodrigocfd
  • 6,450
  • 6
  • 34
  • 68
  • 1
    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. – moessi774 Nov 04 '16 at 11:34
  • 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 Nov 04 '16 at 11:57
  • @moessi774 I just updated me answer, I guess I exactly understood your question now. – rodrigocfd Nov 04 '16 at 13:07
  • 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. – moessi774 Nov 06 '16 at 10:15
  • 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. – Guillermo Prandi Apr 10 '19 at 18:02
  • @Rodrigo Does the solution with interface work when I have nested objects? – Keselme Jul 13 '20 at 12:07
  • @Keselme In theory, yes. – rodrigocfd Jul 13 '20 at 16:38
  • @rodrigocfd what if my interface has fewer fields compared to what is present in the json object – Lakshya Sharma Jun 17 '21 at 12:24
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>"
});
brendon.otto
  • 44
  • 1
  • 8
Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • 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? – moessi774 Nov 06 '16 at 10:13
  • 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. – moessi774 Nov 06 '16 at 10:27
  • I just looked closer to my result of JSON.parse(employeeString) and I saw that it has an additional property Symbol(rxSubscriber)_m.crflse0mxkc which is undefined and I did not send but it is there after parsing. I also get errors when my JSON string is missing properties my object/interface has. This is not the behavior you explained above or am I wrong? – moessi774 Nov 06 '16 at 11:21
  • I'm not sure what you mean by "object string", please elaborate on that. As for the additional properties you have in your object after `JSON.parse` it looks like rx added something into it, not sure at what point, as I have no idea what you're doing and what you're using. Last thing: what errors do you get? do you get them in compilation time or runtime? Please update your question with relevant code and error messages. – Nitzan Tomer Nov 06 '16 at 14:39
  • The object string is an Employee object which gets stringified on the server and then sent to my web app where it should be an object of the employee class there again. Is this enough information or do you need more? I also edited my question with attempts and error. – moessi774 Nov 06 '16 at 19:01
  • 1
    Check my revised answer – Nitzan Tomer Nov 06 '16 at 19:21
  • If I do it like in your example with the new instance I end up with an object which has every character as a own property or so. I edited my question with the error. – moessi774 Nov 07 '16 at 12:39
  • 1
    I can't understand what you mean. Please update your question with this info and explain what you did and what you got back – Nitzan Tomer Nov 07 '16 at 12:41
  • Sorry I commented before updating. Was doing it just at the moment. – moessi774 Nov 07 '16 at 12:44
  • 1
    Using your exact code I get: `Employee {department: "", typeOfEmployee: "", firstname: "", lastname: "", birthdate: ""…}` which is fine – Nitzan Tomer Nov 07 '16 at 13:10
  • Ok now it works. But it still throws me this error: `Argument of type 'Employee' is not assignable to parameter of type 'string'.` And it prints as [Object object] which means to me that is a random object and not one of my specific class. – moessi774 Nov 07 '16 at 13:19
  • I don't get that at all. Are you sure that the code you posted in the question (in your edit) is exactly what you're trying to run? Go to the typescript playground, reproduce the problem there and then share that playground in your question. – Nitzan Tomer Nov 07 '16 at 13:22
  • Yes I am. As employee string I am not even using data from the server anymore. Just the thing you used in you answer. – moessi774 Nov 07 '16 at 13:32
  • Post a playground link – Nitzan Tomer Nov 07 '16 at 13:33
  • In playground it works but I can not get it to work in my program. – moessi774 Nov 07 '16 at 13:44
  • If it works in playground then it works. If it fails in your local environment then you have a completely different problem. I suggest that you ask a new question about it. – Nitzan Tomer Nov 07 '16 at 13:52
  • Ok I will do so. I was so sure I failed in typescript. Thank you for your help anyways I will keep on trying. – moessi774 Nov 07 '16 at 13:54
7
let employee = <Employee>JSON.parse(employeeString);

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

Sefa
  • 8,865
  • 10
  • 51
  • 82
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.

James Skemp
  • 8,018
  • 9
  • 64
  • 107
Alexandr Sargsyan
  • 656
  • 1
  • 6
  • 21
  • 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. – moessi774 Nov 04 '16 at 11:59
2

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));

```

deelde
  • 1,540
  • 13
  • 23
  • 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. – colin moock Aug 15 '20 at 16:02
1

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!

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);
Vlad
  • 3,465
  • 1
  • 31
  • 24
0

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' }

Hesham Yassin
  • 4,341
  • 2
  • 21
  • 23
0

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);

Hadi Nahavandi
  • 666
  • 7
  • 18
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;
    }
Onur Dikmen
  • 339
  • 1
  • 6
  • 17
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

Yurii K
  • 162
  • 1
  • 5
-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

  • '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. – Onur Dikmen Jun 14 '22 at 17:38
  • You should use json parse instead. Json.parse returns any type, but can be converted to an object using the 'as'syntax. – Onur Dikmen Jun 14 '22 at 17:41