Object.assign or declared class properties? - json

I am creating an angular application which accesses the twitch API. The data is returned in varying outlays, some of which I want to deserialize and store in a couple classes.
What I want to know is what are the risks of using Object.assign to instantiate a class compared to manually assigning properties from deserialized json data?
What I currently have:
export class UserDetails implements Serializable<UserDetails>{
public _id: number;
public bio: string;
public created_at: string;
public display_name: string;
public email: string;
public email_verified: boolean;
public logo: string;
public name: string;
public notifications: Object;
public partnered: boolean;
public twitter_connected: boolean;
public type: string;
public updated_at: string;
constructor() {}
deserialize(input: any) {
this._id = input._id;
this.bio = input.bio;
this.created_at = input.created_at;
this.display_name = input.display_name;
this.email = input.email;
this.email_verified = input.email_verified;
this.logo = input.logo;
this.name = input.name;
this.notifications = input.notifications;
this.partnered = input.partnered;
this.twitter_connected = input.twitter_connected;
this.type = input.type;
this.updated_at = input.updated_at;
return this;
}
}
What I could have using Object.assign.
export class UserDetails implements Serializable<UserDetails>{
constructor() {}
deserialize(input: any) {
Object.assign(this, input);
return this;
}
}
I am about to create a new class which will have almost 70 properties.... Is this the best approach?
Afterthought... OR should I actually mix the 2 so I still get intellisense?
export class UserDetails implements Serializable<UserDetails>{
public _id: number;
public bio: string;
public created_at: string;
public display_name: string;
public email: string;
public email_verified: boolean;
public logo: string;
public name: string;
public notifications: Object;
public partnered: boolean;
public twitter_connected: boolean;
public type: string;
public updated_at: string;
constructor() {}
deserialize(input: any) {
Object.assign(this, input);
return this;
}
}

In my opinion, having the public class level fields would be beneficial for two reasons:
It should provide intellisense.
Using any non declared field, should raise an error (this is also applicable if no field is declared). Note that using Object.assign can add non declared fields to the object.
Example (fiddle link):
class Person {
firstName;
lastName;
deserialize(input: any) {
Object.assign(this, input);
return this;
}
}
let p = new Person();
p.deserialize({firstName:"John", lastName:"Doe", sex:"M"});
console.log(p); //<-- logs: Person { firstName: 'John', lastName: 'Doe', sex: 'M' }
console.log(p.sex); //<-- this logs 'M' to console in JSFiddle.
Note that the the propoerty sex is added to p, however, accessing the property directly like it is done in the 2nd console.log should raise the following compilation error (though it works on JSFiddle, not exactly sure why):
TSError: тип Unable to compile TypeScript
nodesrc\test.ts (13,15): Property 'sex' does not exist on type 'Person'. (2339) ...
So if you would like to go with this kind of type-safety then declaring the fields might be helpful.
Hope this helps.

Related

Map a json response to an object - Angular

I have this class:
export class Person {
constructor(
public url: string,
public name: string,
public gender: string,
public culture: string,
public aliases: string[]
) {}
}
I'm trying to get an Array of type Person with a get function to an open API (JSONPlaceHolder).
In my person.component.ts I have the method:
getPeople(): void {
this.myService.getPeople().subscribe(people: Person[]) =>
this.allPeople = people
}
And in the service:
getPeople(): Observable<Person[]>{
return this.http.get('url of the api').pipe(map((r: any) =>
r.results.map((p: any) =>
new Person(p.id, p.name, p.username, p.email)
)
))
}
but I don't get anything.
What am I doing wrong?

How to map an angular 2 class from an http call

I'm new in Angular.
I've a class called User:
export class User {
private id: number;
private name: string;
private surname: string;
get Id(): number {
return this.id;
}
set Id(newId: number) {
this.id = newId;
}
get Name(): string {
return this.name;
}
set Name(newName: string) {
this.name = newName;
}
get Surname(): string {
return this.surname;
}
set Surname(newSurname: string) {
this.surname = newSurname;
}
}
...a function to retrive an array of user:
getValues() {
this.usersService.getUsers()
.subscribe((users: User[]) => this.dataSource = users);
}
and a method to retrive the users array from backend WebApi:
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.usersSearchUrl)
.pipe(
tap(users => this.log(`fetched users`)),
catchError(this.handleError('getUsers', []))
);
}
finally the json returned from the webapi:
[{"id":"1","name":"Alberico","surname":"Gauss"},{"id":"2","name":"Anassimandro","surname":"Dirac"},{"id":"3","name":"Antongiulio","surname":"Poisson"}]
I would have expected that the call would automatically mapped the User class, instead it only gives me an array of type User, in fact if I write something in my component .subscribe((utenti: Utente[]) => console.log(utenti[0].Surname)); the console writes me "undefined". Can you tell me where I'm wrong? Thanks
You are retrieving JSON from your backend, as is expected. A Javascript (or typescript) class is not the same thing.
When the JSON is returned, it can be automatically converted into a simple JSON object in Javascript but it will NOT include all your getters and setters. So these class methods are not available, which is why you get undefined.
Remove all the getters and setters and add a constructor. Then you can just call Surname directly as a property and it will return the value (since it will then just be a plain JSON object).
export class User {
constructor() {
}
public id: number;
public name: string;
public surname: string;
}
Or without a constructor, and just declare the properties directly:
export class User {
public id: number;
public name: string;
public surname: string;
}
Or you could also use an interface:
export interface User {
id: number;
name: string;
surname: string;
}
You can read more about this issue here and here.
I think in component ts use like this code:
users: User[];
constructor(
private us: usersService,
public auths: AuthService
)
this.us.getUsers.subscribe(
users=> {
this.users= users.map((user) => {
return new User(user);
});
}
);
In service I think to write:
public getUsers(): Observable<User[]> {
let headers = new Headers();
headers.append('x-access-token', this.auth.getCurrentUser().token);
return this.http.get(Api.getUrl(Api.URLS.getUsers), {
headers: headers
})
.map((response: Response) => {
let res = response.json();
if (res.StatusCode === 1) {
this.auth.logout();
} else {
return res.StatusDescription.map(user=> {
return new User(user);
});
}
});
}
For me this logic work perfect. I hope to help you with this code

object destructuring on class members

Using ES6, I have a class in which I'm defining some variables and a function that will take an object and assing my variables to the values of it. This is repetitive, so is there any way I can use destructuring assingment to achieve this?
class BasicDataMgmt {
public id: number;
public name: string;
public email: string;
public phone: string;
public type: string;
fetchData(data) {
this.id = data.id;
this.name = data.name;
this.email = data.email;
this.phone = data.phone;
this.type = data.type;
}
}
It can be
fetchData(data) {
Object.assign(this, data);
}
for unsanitized data. Or
fetchData({ id, name, ... }) {
Object.assign(this, { id, name, ... });
}
for sanitized data.
Using Lodash _.pick is beneficial here for doing Object.assign(this, _.pick(data, ['id', 'name', ...])).

TypeScript Constructor Overload with Empty Constructor

Why is it not allowed to have separate constructor definitions in TypeScript?
To have e.g. two constructors, I need to write my code like this.
constructor(id: number)
constructor(id: number, name?: string, surname?: string, email?: string) {
this.id = id;
this.name = name;
this.surname = surname;
this.email = email;
}
Thereby I need to put ? after the parameters that are not required in the first constructor.
Why can't I write it like this?
constructor(id: number) {
this.id = id;
}
constructor(id: number, name: string, surname: string, email: string) {
this.id = id;
this.name = name;
this.surname = surname;
this.email = email;
}
So that for both constructors all parameters are mandatory.
Moreover, if I need to have an empty constructor things get even weirder, since I need to mark every parameter with a ?.
constructor()
constructor(id?: number, name?: string, surname?: string, email?: string) {
this.id = id;
this.name = name;
this.surname = surname;
this.email = email;
}
Why does TypeScript differs from common languages like C# or Python here?
I would expect it to work like this.
constructor() {
}
constructor(id: number, name: string, surname: string, email: string) {
this.id = id;
this.name = name;
this.surname = surname;
this.email = email;
}
So you can pass none parameter or must pass all parameters.
Because your constructor implementation is called by all your overload constructors. (Technically, at runtime there's only one constructor function that gets called with the various overload argument signatures.)
Imagine it like this:
overload_constructor(id:string) {
implementation_constructor(id);
}
implementation_constructor(id:string, name?:string, age?:number) {
// ...
}
Thinking of it this way, overload_constructor could not call implementation_constructor unless name and age are optional.
Also see Basarat's answer, the implementation isn't exposed for public usage by the type checker (though at runtime it's the "real" constructor used in JS). If you want to only allow (), (id), or (id, name, surname, email) as the only valid call signatures you would do it like this:
constructor()
constructor(id: number)
constructor(id: number, name: string, surname: string, email: string)
constructor(id?: number, name?: string, surname?: string, email?: string) {
this.id = id;
this.name = name;
this.surname = surname;
this.email = email;
}
Note that in the implementation all parameters are optional, but that signature is not exposed when compiling and you can only use these these calls:
new Foo()
new Foo(1)
new Foo(1, "a", "b", "c")
Not, for example:
new Foo(1, "a")
The last function overload is only used in the implementation and not available publicly. This is shown below:
class Foo{
constructor()
constructor(id?: number) {
}
}
const foo1 = new Foo();
const foo2 = new Foo(123); // Error! : not public
If you want id:number to be available publically ofcourse you can add another overload:
class Foo{
constructor()
constructor(id: number)
constructor(id?: number) {
}
}
const foo1 = new Foo();
const foo2 = new Foo(123); // Okay
const foo3 = new Foo('hello'); // Error: Does not match any public overload
The reason is that TypeScript tries not to do fancy code generation for function overloading (traditional languages do this using name mangling e.g. C++)
So you can pass none parameter or must pass parameters.
Actually you can make the final overload optional but none of the public ones as optional. Consider the following example:
class Foo{
constructor(id: number, name:string)
constructor(name:string)
constructor(idOrName?: number|string, name?:string) {
}
}
const foo1 = new Foo('name'); // Okay
const foo2 = new Foo(123); // Error: you must provide a name if you use the id overload
const foo3 = new Foo(123,'name'); // Okay
You can use Builder pattern to solve this. Even in C# or Python, it quickly becomes a better approach as the number of constructor arguments grows.
class Foo {
constructor(public id: number, public name: string, public surname: string, public email: string) {
}
static Builder = class {
id: number = NaN;
name: string = null;
surname: string = null;
email: string = null;
Builder() {
}
build(): Foo {
return new Foo(this.id, this.name, this.surname, this.email);
}
}
}
If you use static methods to implement overload contructors, see.
export class User implements IUser {
constructor(
private _id: string,
private _name: string,
private _email: string,
) {}
static New(jsonUser:string){
return new User(
JSON.parse(jsonUser).id,
JSON.parse(jsonUser).name,
JSON.parse(jsonUser).email)
}
}

Creating JSON objects in Typescript

How to create/save an array of JSON objects only when there is a new item?
The problem I am having is:
How can I create/save JSON objects directly or do I have to have a corresponding class object created?
What Is the best way to check if particular item exists or not?
You can store the json in variables of class any but usually you will want to have some typing on these object so what I usually do is having a fromJson static method on my model classes that will instantiate an object from a json like that:
class User {
public firstname: string;
public lastname: string;
public age: number;
constructor() {
}
public static fromJson(userJson: any): User {
var user = new User();
this.firstname = userJson.firstname;
this.lastname = userJson.lastname;
this.age = userJson.age;
return user;
}
}
If one of your properties is a class you can also have a from json on it and embed it in the from json like that:
class User {
public firstname: string;
public lastname: string;
public status: Status;
constructor() {
}
public static fromJson(userJson: any): User {
var user = new User();
this.firstname = userJson.firstname;
this.lastname = userJson.lastname;
this.status = Status.fromJson(userJson.status);
return user;
}
}
Hope it helps