JSON to Javascript Class - javascript

I have a http request that gets this Json object from a nosql database:
let jsonBody = {
birthday : 1997,
firstname: 'foo',
lastname:'bar'
}
Then I want to load this information into the Student model:
class Student{
constructor(){
}
getFullname(){
return this.lastname+' '+this.firstname
}
getApproxAge(){
return 2018- this.birthday
}
}
Normally, I would add this method to this class:
fromJson(json){
this.studentId = json.studentId;
this.birthday = json.birthday;
this.firstname = json.firstname;
this.lastname = json.lastname;
}
I would use it as follow:
let student = new Student()
student.fromJson(jsonBody)
console.log(student.getFullname())
console.log(student.getApproxAge())
This works fine but my problem is I have: 100 proprieties in reality. Will I have to write all proprities one by one in the fromJson method?
And also, if a propriety name has change, let's say: lastname became LastName, I will have to fix it?
Is there a simpler way to just assign these values to the object student dynamically but keep all of its methods??
Something like this:
fromJson(json){
this = Object.assign(this, json) //THIS IS NOT WORKING
}

Just assign to an instance:
static from(json){
return Object.assign(new Student(), json);
}
So you can do:
const student = Student.from({ name: "whatever" });
Or make it an instance method and leave away the assignemnt:
applyData(json) {
Object.assign(this, json);
}
So you can:
const student = new Student;
student.applyData({ name: "whatever" });
It could also be part of the constructor:
constructor(options = {}) {
Object.assign(this, options);
}
Then you could do:
const student = new Student({ name: "whatever" });
And also, if a property name has changed, let's say: lastname became LastName, I will have to fix it?
Yes you will have to fix that.

There is no way in javascript to deserialize json into classes. So I wrote a library ts-serializable that solves this problem.
import { jsonProperty, Serializable } from "ts-serializable";
export class User extends Serializable {
#jsonProperty(String)
public firstName: string = ''; // default value necessarily
#jsonProperty(String, void 0)
public lastName?: string = void 0; // default value necessarily
#jsonProperty(Date)
public birthdate: Date = new Date(); // default value necessarily
public getFullName(): string {
return [
this.firstName,
this.lastName
].join(' ');
}
public getAge(): number {
return new Date().getFullYear() - this.birthdate.getFullYear();
}
}
const user: User = new User().fromJSON(json);
user.getFullName(); // work fine and return string
user.getAge(); // work fine and return number
// or
const user: User = User.fromJSON(json);
user.getFullName(); // work fine and return string
user.getAge(); // work fine and return number
The library also checks types during deserialization.

Related

Preserve instance type when saving to localStorage

I have a hard time figuring out how I should save my Class to localStorage such as when I retrieve it back, I can still call the instance methods I defined.
Here's an example that throws an error:
class Employee {
constructor(name, gender, department, yy, email) {
this.name = name;
this.gender = gender;
this.department = department;
this.email = email;
this.skills = [];
}
addNewSkill(skill){
this.skills.push(skill);
}
}
const employee = new Employee({
name: "John Doe",
gender: "male",
department: "CS",
email: "john#doe.com"
});
employee.addNewSkill("coding");
localStorage.setItem("employees", employee);
const retrievedEmployee = JSON.parse(localStorage.getItem("employee"));
// Throws Uncaught TypeError: retrievedEmployees[0].addNewSkill is not a
// function.
retrievedEmployee.addNewSkill("reading");
Objects lose their type when you turn them into strings using JSON.stringify. While their properties and values are preserved, any information about the fact that it's an Employee instance is lost.
After retrieval from window.localStorage, when you JSON.parse the saved string, you are creating plain objects instead of actual Employee objects therefore the retrieved objects lack the employee.addNewSkill() method.
You should ensure that your Employee class allows easy instantiation using Employee-like objects, then each time you retrieve a saved Employee from LocalStorage you instantiate a new Employee instance out of that object, like so:
Here's an example:
// The Employee now takes an Employee or Employee-like object
// instead of separate arguments.
class Employee {
constructor({ name, gender, department, yy, email, skills }) {
this.name = name;
this.gender = gender;
this.department = department;
this.email = email;
this.skills = skills || [];
}
addNewSkill(skill){
this.skills.push(skill);
}
}
// Create an Employee.
const employee = new Employee({
name: "John Doe",
gender: "male",
department: "CS",
email: "john#doe.com"
});
// Add a skill.
employee.addNewSkill("coding");
// Turn employee to string and save to localStorage.
localStorage.setItem("employee", JSON.stringify(employee));
// Parse the string back into an Employee-like object and
// use that object to construct a new Employee.
const retrievedEmployee = new Employee(JSON.parse(localStorage.getItem("employee")))
// Add another skill, using the Employee instance method.
retrievedEmployee.addNewSkill("reading");
// Logs Employee having 2 skills, one added *before* we saved to localStorage
// and another skill we added *after* we retrieved him from localStorage.
console.log(retrievedEmployee);
For the record, I've had this problem before in a largeish project and used an npm module called typeson which allowed serialising and deserialising custom types.
You could do it this way, as Nik Kyriakides said in their comment. It would be used as such:
const Employee = require('./path/to/Employee.js');
const employee = new Employee('Jane Doe', 'Female', 'Development', 'jdoe#acme.com', [ 'html', 'css' ]);
employee.addNewSkill('javascript');
console.log(employee.skills);
Alternatively, you could use a setter:
...
set skills (skill) {
this.skills.push(skill);
}
...
And then use it as:
const Employee = require('./path/to/Employee.js');
const employee = new Employee('Jane Doe', 'Female', 'Development', 'jdoe#acme.com', [ 'html', 'css' ]);
employee.skills = 'javascript';
console.log(employee.skills);
But syntactically, this reads as though it should be setting the array, not appending to it, so it's probably not a good choice for this context. I did want to mention it here though to give you some ideas of how you could get and set other class variables.
In your example, I would set this up in the following way:
Employee.js
module.exports = class Employee {
constructor(name, gender, department, yy, email, skills) {
this.name = name;
this.gender = gender;
this.department = department;
this.email = email;
this.skills = [];
}
addNewSkill(skill){
this.skills.push(skill);
}
}
And then to work with your employee list, I would do something like this, which may need some tweaking, but just to give you an idea:
index.js
const Employee = require('./path/to/Employee.js');
const fs = require('fs');
const employees = fs.readFileSync('employees.txt').toString().split("\n");
let employeeList = [];
employees.forEach(employeeData => {
const employee = JSON.parse(employeeData);
const { name, gender, department, email, skills } = employee;
employeeList.push(new Employee(name, gender, department, email, skills));
})
//do stuff with employeeList

Angular - Best approach for sending partial data model in HTTP request

Server returns a User object in response
{
name: "some_name",
age: 30
}
I'll create a class for the User object, but the class also have other property which I need only in the client side
class User {
public color: string = "red"
constructor(public name: string, public age: number) {}
}
color property I'll only use in client-side.
Now User type for client and server are different.
If I want to send User obj back to the server without color property, in request method
Should I create a new class Object from User?
class UserRequest {
constructor(public name: string, public age: number) {}
}
let userRequest = new UserRequest(user.name, user.age);
Just create new object without type?
let userRequest = {
name: user.name,
age: user.age
}
Here I have only 2 property, If I have more then will this feasible?
Assume If I have to send a list of User then iterating all the user object creating a new object will increase complexity.
What is the best approach when types are different?
You can define a new function in your User class that will return only the database fields, or make it more generic by defining a dbFields array that contains only the fields that need to be sent to the server:
class User {
public color: string = "red";
private dbFields = ['name', 'age'];
constructor(public name: string, public age: number) {}
getUserData(){
let data = {};
for(const field of this.dbFields)
data[field]=this[field];
return data;
}
}
Then you can simply call user.getUserData() and it will return an object with only name and age
You can also make it more general by defining a Model class and use the function in other models as well:
class Model {
constructor(protected dbFields: string[]) {}
getDatabaseData(){
let data = {};
for(const field of this.dbFields)
data[field]=this[field];
return data;
}
}
class User extends Model {
public color: string = "red";
constructor(public name: string, public age: number) {
super(['name', 'age']);
}
}
And you call user.getDatabaseData() when you want to send a request.
If you have more fields, you just add more values to the dbFields array. If you have an array of users, you can map it to an array of database data:
const usersData = users.map((user: User)=>user.getDatabaseData());
this.http.post<ServerModel>({ serverModel.prop1: clientModel.prop1, serverModel.prop2: clientModel.prop2 });
If you need to create server model in more than one spot you can wrap it in a function. Seeing you are using classes you could have a method called toServerModel but the Angular standard is to use interfaces for data models, not classes.

Passing objects between Components/Pages

Hello Im passing objects between two pages.
I have two pages in Ionic App. The first page has Defect object and sends it to the second page. The second Page receives the object and calls it's methods. Passing objects is done with usage of NavParams, an Ionic core class. Below you can see the receiving of the object. The Defect object.
export class Defect {
public priority: DefectPriority;
public state: DefectState;
public note: string;
public _id: string;
public posX: number;
public posY: number;
public createdAt: number;
public drawingId: string;
public images: DefectImage[];
constructor();
constructor(posY?: number, posX?: number, note?: string, defectId?: string, drawingId?: string) {
if (defectId === undefined || defectId === "") {
throw new Error("incorrect defect id");
}
if (posX === undefined || posY === undefined) {
throw new Error("incorrect coordinates");
}
if (drawingId === undefined || drawingId === "") {
throw new Error("incorrect drawingId");
}
if (drawingId === undefined || drawingId === "") {
throw new Error("incorrect drawingId");
}
this.priority = DefectPriority.NORMAL;
this.createdAt = new Date().getTime();
this.state = DefectState.REPORTED;
this._id = defectId;
this.note = note;
this.posX = posX;
this.posY = posY;
this.drawingId = drawingId;
this.images = [];
}
public getPriority() {
return this.priority;
}
setPriority(value: DefectPriority) {
if (!Object.values(DefectPriority).includes(value.toString())) {
throw new Error("incorrect priority")
}
this.priority = value;
}
public changeState(value: DefectState) {
this.state = value;
}
public setNote(note: string) {
this.note = note;
}
generateImageUrl(creatorName: string): DefectImage {
const newUrl = ObjectId.generate() + '-' + this._id + '.jpg';
const defectImage = new DefectImage(newUrl, creatorName, new Date().getMilliseconds());
this.addImage(defectImage);
return defectImage;
}
addImage(defectImage: DefectImage) {
if (!this.images) {
this.images = [];
}
this.images.push(defectImage);
}
Here is the receiving class:
defect: Defect;
constructor(private viewCtrl: ViewController,
private nav: NavParams,
private navCtrl: NavController,
private photo: PhotoProvider) {
this.defect = this.nav.get('defect');
}
Defect class apart from some properties has also methods like: generateImageUrl
Now when I change view to the component where the defect is beeing fetched from params internally it is just JS object without information about Defect class methods: Which means I cannot call methods defined in defect class after I send it to the another Page.
Notice no custom methods like generateImageUrl. Is there a way that I could not lose informations about this object? Or should I just recreate this object from data in the new component ?? my goal on screen below:
the way Im passing data:
const defectModal = this.modalCtrl.create(DefectDetailModal, {
defect: this.defect
});
I'm assuming that Defect is an entity in your App. Angular's Style Guide recommends using interfaces for data models instead of classes.
Consider using an interface for data models.
That being said, you should have created a DefectService, where you would have set some property for the current defect that you're dealing with.
You could have then injected the service in the components that you wanted to share data between. Then from one component, you could have set the defect and then you could get the defect in the other component using setters and getters.

Add new object to array angular

I'm trying to put a JSON object into an array after an API call.
First I made my API call and then I try to add every user into in a formatted JSON object.
connectionProvider.ts
import { UserModelProvider } from './../user-model/user-model';
import { MSAdal, AuthenticationContext, AuthenticationResult } from '#ionic-native/ms-adal';
export class MsConnectionProvider {
userInfo : UserModelProvider;
users: UserModelProvider[];
constructor(...) {}
getUserInfo(){
let header = new Headers({
Authorization: this.accessToken;
});
let options = new RequestOptions({headers: header});
this.usersSubscription = this.http.get("https://graph.microsoft.com/v1.0/groups/**ID**/members", options).map(res => res.json()['value']);
this.usersSubscription.subscribe(res => {
for (let user of res){
this.addUserInfo(user.displayName, user.jobTitle, "something", user.mail);
}
});
}
addUserInfo(name, job, departement, mail){
this.userInfo = new UserModelProvider;
this.userInfo.name = name;
this.userInfo.job = job;
this.userInfo.departement = departement;
this.userInfo.mail = mail;
this.users.push(this.userInfo);
}
}
userModelProvider.ts
export class UserModelProvider {
name: string;
job: string;
departement: string;
mail: string;
photo: any;
}
The problem is when I try to push "this.userInfo = new UserModelProvider" into this.users array the function block and nothing happens.
I certainly don't understand the class, can you help me?
Thank you.
You can't push to an array that has not been initialised.
you need to change:
users: UserModelProvider[]
to:
users: UserModelProvider[] = [];
Also (may or may not help):
Nothing is probably happening because push mutates the array and as such angular change detection may not kick in.
Instead of using push, create a new array with:
this.users = [...this.users, this.userInfo]
or in ES5:
this.users = this.users.concat([this.userInfo])
Create an instance of the class before assigning the values,
this.userInfo = new UserModelProvider();

How to map JSON data to a class

I created a ES6 class by Babel and I want to map JSON data which is gotten from a server to the ES6 class.
Is there anything common way to do that?
User.js
export default class User {
constructor() {
this.firstName;
this.lastName;
this.sex;
}
}
app.js
import User from "./classes/User";
var data = JSON.parse(req.responseText);
console.log(data.firstname); //Bob
//now...just set data one by one?
I would merge the JSON object into this using Object.assign, as follows:
class User {
firstName;
lastName;
sex;
constructor(data) {
Object.assign(this, data);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
}
}
var data = JSON.parse(req.responseText);
new User(data);
you can use this npm package https://www.npmjs.com/package/class-converter to map all JSON to a class.
it looks like following one:
import { property, toClass } from 'class-convert';
class UserModel {
#property('i')
id: number;
#property()
name: string;
}
const userRaw = {
i: 1234,
name: 'name',
};
// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
{
id: 1234,
name: 'name',
}

Categories

Resources