Angular 9 mapping a json response to array - json

I have this interface
export interface Student {
cf: string,
firstName: string,
lastName: string,
dateOfBirth: Date,
description?: string,
enrollmentDate?: Date
}
I want to populate an array of students with a http get request, which returns the following json for each student
{cf: "blablabla", first_name: "Mario", last_name: "Rossi", date_of_birth: "1998-01-24", enrollment_date: "2019-03-20" },
As you can see, the interface has different names from the response (firstName instead of first_name), so when I print to the console the names of the students I get undefined.
This is the service function from which I get the data
getStudents(): Observable<Student[]> {
return this.httpClient.get<Student[]>(this.studentsUrl, this.baseService.httpOptions);
}
And here is my students component
export class StudentsComponent implements OnInit {
students: Student[];
childIcon = faChild;
plusIcon = faPlus;
private _newStudent: boolean = false;
constructor(private studentsService: StudentsService) { }
ngOnInit(): void {
this.studentsService.getStudents().subscribe(
(result: Student[]) => {
this.students = result;
this.students.forEach(student => console.log(student));
},
error => console.log(error)
)
}
}
Is there a way to convert the json response to my Student interface? Several answers on stack overflow suggest map is the way, but I don't understand how to use that operator alog with subscribe

One way would be manually loop through the array and define new keys and delete obsolete ones before returning the array using RxJS map.
Service
import { pipe } from 'rxjs';
import { map } from 'rxjs/operators';
getStudents(): Observable<Student[]> {
return this.httpClient.get<Student[]>(this.studentsUrl, this.baseService.httpOptions).pipe(
map(response => response.forEach(student => {
student.firstName = student.first_name;
student.lastName = student.last_name;
student.dateOfBirth = student.date_of_birth;
student.enrollmentDate = student.enrollment_date;
delete student.first_name;
delete student.last_name;
delete student.date_of_birth;
delete student.enrollment_date;
});
)
);
}
But depending on the number of elements in the array, this could be a heavily taxing operation for a single HTTP request. Couldn't you define the interface definition to match the one of the API?

Related

Assign JSON response object value to Angular model class object

I'm getting the following response from an API.
{
'firstName' : 'Sam',
'lastName' : 'Thomson',
'employeeAge' : 12
}
In my Angular code, I'm trying to map JSON response to Angular class model. Here is the class model.
export class Employee{
constructor(){
this.empage = 0;
}
public firstName : String;
public lastName : String;
public empage : Number;
}
Here is the code for API call.
this.http.get('/api').subscribe((result : Employee) =>{
let emp = new Employee();
Object.assign(emp, result);
console.log('Result is ', emp);
})
The output received is :
{empage: 0, firstName: "Sam", lastName: "Thomson", age: 12}
As seen above the age from response doesn't get mapped to empage from the model instance. How to achieve the same without making the property names same?
The expected output :
{empage: 12, firstName: "Sam", lastName: "Thomson"}
You can use the overloaded version of Object.assign method which accepting three-parameters, you can pass the third value as an object for which you want to override the values Reference link:
Just use:
Object.assign(emp, result, {empage: result.employeeAge});
Two things to note
In statement this.http.get('/api').subscribe((result: Employee) the response is assumed to be of type Employee, whereas it isn't. It is better to replace it with result: any.
As far as I know, there is no native way to map an object of one type to another type. The following is a workaround for your specific requirement using Object.keys()
export class AppComponent implements OnInit {
private employeeMap = ((source): Employee => {
const result = new Employee();
Object.keys(source).map((key) => {
if (key === 'employeeAge') {
result['empage'] = source[key];
} else {
result[key] = source[key];
}
});
return result;
});
ngOnInit() {
this.jsonService.getData().subscribe(
(result: any) => {
let emp: Employee = this.employeeMap(result);
console.log(emp);
}
);
}
}
Working example: Stackblitz

create object structure of type from JSON.parse object [duplicate]

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!
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:
Copying AJAX JSON object into existing Object
Parse JSON String into a Particular Object Prototype in JavaScript
In essence, you'd just :
var d = new MyRichObject();
d.copyInto(jsonResult);
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.
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.
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.
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
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
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.
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.
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;
}
}
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"
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.
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
If you need to cast your json object to a typescript class and have its instance methods available in the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:
Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)
Use 'as' declaration:
const data = JSON.parse(response.data) as MyClass;
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(); }
}
You can create an interface of your type (SomeType) and cast the object in that.
const typedObject: SomeType = <SomeType> responseObject;
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
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.
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
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
}
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 :
conversion schema - allowing to map between fields and determine
how the conversion will be done
Classes map array
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
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
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',
// }
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.
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
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
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'});
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

Typescript JSON deserialize to interface missing methods

I have a set of classes with a fairly straightforward set of fields:
interface IQuiz {
name: string;
questions: Question[];
score(): number;
}
export class Quiz implements IQuiz {
name: string;
questions: Question[];
score(): number {
var theScore: number = 0;
this.questions.forEach(question => (theScore += question.value));
return theScore;
}
}
interface IQuestion {
text: string;
value: number;
}
export class Question {
text: string;
value: number;
}
Then I have some JSON which represents some instances of those objects. This JSON includes values for Quiz.name, an array of Quiz.questions each with values for their text and value fields.
Somewhere else, I have a reference to this Quiz class that has been created from JSON.parse(json) like the code below:
var json = '{"name": "quizA", "questions": [{"text": "Question A", "value": 0 }, ... ]}'
var quiz = <IQuiz>JSON.parse(json);
// Some other stuff happens which assigns values to each question
var score = quiz.score(); // quiz.score is not a function
It seems the deserialized object is not actually an implementation of the interface provided.
How do I get a reference to an instance of the correct interface, so that I can call quiz.score() and it actually call the method?
There is no need of interfaces, it will add twice the work for maintaining your model's properties. Add a constructor in your Quiz class so that it extends your json object.
export class Quiz {
constructor( json: any )
{
$.extend(this, json);
// the same principle applies to questions:
if (json.questions)
this.questions = $.map( json.questions, (q) => { return new Question( q ); } );
}
name: string;
questions: Question[];
score(): number { ... }
}
In your ajax callback, create the Quiz object from your json Object:
let newQuiz = new Quiz( jsonQuiz );
let score = newQuiz.score();
I detailed the solution in that post.

Parse complex json objects with TypeScript

How can I parse complex json object with TypeScipt ?
I have a customer object, who have some invoices.
This is my model :
export class Customer {
public id: string;
public name: string;
public invoices: CustomerInvoice[];
get invoicesCount(): number {
if (this.invoices== null) {
return 0;
}
return this.invoices.length;
}
constructor() {
}
}
export class CustomerInvoice {
public id: number;
constructor() {
}
}
And in my service I have :
ngOnInit() {
if (this.id != null) {
this.dataService.getCustomer(this.id).subscribe(data => {
this.customer = data;
},
err => console.log(err));
}
}
Customer data are great (my customer id, name etc have some values) but the invoices are null.
The json is correct, data.Invoices.length return a number.
How can I parse complex json object with TypeScipt ?
Assuming you mean parsing JSON into actual class instances instead of simple Javascript objects, TypeScript does not ship this feature off-the-shelf.
You may create an interface declaration using which you can do a type-assertion (not a type-cast) to somewhat mimick type-safety if the JSON is trusted, but that's it -- I know no native tool to serialize a JSON to actual instances of user-defined types.
interface ICustomerInvoice {
id: number;
}
interface ICustomer {
id: string;
name: string;
invoices: ICustomerInvoice[];
}
var customer: ICustomer = JSON.parse(json) as ICustomer;
Nevertheless, for the same obvious reasons I began putting together TypedJSON to introduce this feature into TypeScript. You can annotate your classes and members with JsonObject and JsonMember decorators:
#JsonObject
export class CustomerInvoice {
#JsonMember
public id: number;
}
#JsonObject
export class Customer {
#JsonMember
public id: string;
#JsonMember
public name: string;
#JsonMember({ elementType: CustomerInvoice })
public invoices: CustomerInvoice[];
get invoicesCount(): number {
if (this.invoices== null) {
return 0;
}
return this.invoices.length;
}
}
To deserialize a JSON-string, you would use TypedJSON.parse instead of JSON.parse, the getter will also be present as expected:
var customer = TypedJSON.parse(json, Customer);
typeof customer.invoicesCount; // "number"
It is recommended to be used with ReflectDecorators (but not required). If you choose to skip this recommendation, you also need to specify the 'type' setting for members, for example:
#JsonMember({ type: String })
public id: string;
You could use the rxjs's map operator to manually map the entities in you DataService.
I haven't tested the code, so it may be that you'll need to map the invoices as well with rx (invoices.map( ... )) to iterate the collection, but the principle remains the same.
getCustomer(id: number): Observable<Customer> {
return this.http.get<Customer>(this.customerUrl).map(customer => {
let newCustomer = customer;
newCustomer.invoices = customer.invoices;
return newCustomer;
});
}

How do I cast a JSON Object to a TypeScript class?

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!
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:
Copying AJAX JSON object into existing Object
Parse JSON String into a Particular Object Prototype in JavaScript
In essence, you'd just :
var d = new MyRichObject();
d.copyInto(jsonResult);
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.
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.
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.
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
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
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.
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.
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;
}
}
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"
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.
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
If you need to cast your json object to a typescript class and have its instance methods available in the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:
Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)
Use 'as' declaration:
const data = JSON.parse(response.data) as MyClass;
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(); }
}
You can create an interface of your type (SomeType) and cast the object in that.
const typedObject: SomeType = <SomeType> responseObject;
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
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.
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
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
}
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 :
conversion schema - allowing to map between fields and determine
how the conversion will be done
Classes map array
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
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
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',
// }
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.
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
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
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'});
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