I'm using TypeScript to build an app and I'm making API calls to retrieve objects. For instance, I have a TypeScript User Object like this:
export class User {
id : number;
name : string;
email : string;
}
And my API returns
{
"id" : 3,
"name" : "Jonn",
"email" : "john#example.com"
}
I want to convert that JSON to a User. I've read in another posts I can do this:
let user : User = <User> myJson;
This seemly works. I can access properties of the user like user.namebut my problem is that, if the User class implements some method, the properties are not available. For example, if inside the User class I have this:
getUppercaseName() : string {
return this.name.toUppercase();
}
This happens:
user.name returns John but user.getUppercaseName() returns undefined
What's going on? How to solve this
What you are doing it treating classes as interfaces, as this will work exactly the same:
export interface User {
id : number;
name : string;
email : string;
}
The reason that the compiler doesn't complain about you using classes this way is because:
One of TypeScript’s core principles is that type-checking focuses on
the shape that values have. This is sometimes called “duck typing” or
“structural subtyping”
(read more about duck typing)
Or with an example:
class User {
id: number;
name: string;
email: string;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
}
function logUser(user: User) {
console.log(`user id: ${ user.id }, name: ${ user.name }, email: ${ user.email }`);
}
logUser({
id: 1,
name: "user 1",
email: "mailaddress"
});
logUser(new User(2, "user 2", "anotheraddress"));
In the two calls to logUser I pass objects that satisfy the interface of the User class.
If you want to have an instance of that class instead of an object that satisfies it then you should do something like:
new User(myJson.id, myJson.name, myJson.email);
And have a constructor like in my example, or:
interface IUser {
id: number;
name: string;
email: string;
}
class User implements IUser {
id: number;
name: string;
email: string;
constructor(data: IUser) {
this.id = data.id;
this.name = data.name;
this.email = data.email;
}
}
...
new User(myJson);
Nitzan pretty much explained the theory behind this, so I'll just provide an alternative approach:
interface UserInfo {
id: number;
name: string;
email: string;
}
class User {
userInfo: UserInfo;
constructor(userInfo: UserInfo) {
this.userInfo = userInfo;
}
getUpperCaseName(): string {
return this.userInfo.name.toLocaleUpperCase();
}
}
const json = {
id: 3,
name: "Jonn",
email: "john#example.com"
}
let user: User = new User(json);
There is a problem when your User has 50 or more properties...
Add a constructor in your User object so that it extends your json object.
export class User {
constructor( jsonUser: any )
{
$.extend(this, jsonUser);
}
id : number;
name : string;
email : string;
getUpperCaseName() {...}
}
In your ajax callback, create the User object from your json Object:
let receivedUser = new User( jsonUser );
let userName = receivedUser.getUpperCaseName();
I detailed the solution in that post.
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
How do you create a variable of type AccessGroup
declare class AccessGroup {
id: number;
active: boolean;
display_name: string;
description: string;
access_group_features: [];
created_at?: Date;
created_by?: string;
updated_at?: Date;
updated_by?: string;
static fromJSON(json: any): any;
toJSON(): this & {
access_group_features: any;
};
}
What i thought is that we can do it as such
let x:AccessGroup={
id:1,
active:false,
display_name:'',
description:'',
access_group_features:[]
toJSON(): ?
}
but it gives an error as assigning something to toJSON() is mandatory and don't know about that except the fact the & works like a intersection if I'm not wrong
Can someone give an example as to how toJSON() can be defined for a variable
You just implement it the same way as you would in a class. That means that you return an object that is based on the object running the method, plus an extra property.
That would look like { ...this, extraProp: 'whatever' }
declare class AccessGroup {
id: number;
active: boolean;
display_name: string;
toJSON(): this & {
access_group_features: any;
};
}
let x: AccessGroup = {
id: 123,
active: true,
display_name: 'Testing 123',
toJSON() {
return {
...this,
toJSON: this.toJSON, // Make typescript happy
access_group_features: 'Test'
}
}
}
And as long as you call that method like x.toJSON() then this will be the x object and everything will work correctly.
As you probably noted though, I needed to explicitly pass in the toJSON method as that wasn't getting included in ...this. I believe this is because in a class the methods are not enumerable. Methods are stored on the class prototype because they do not change per instance. this means that typescript does not believe that toJSON will be a property of { ...this }
At least I think, this one is a bit tricky.
Playground
But having any function in the return of a toJSON() probably isn't right either. So you probably don't want to include that at all.
If you change the type to something like:
toJSON(): Omit<this, 'toJSON'> & {
access_group_features: any;
};
Then you can leave out the method:
toJSON() {
return { ...this, access_group_features: 'Test' }
}
Playground
But creating a plain object to implement a class interface with methods isn't ideal. If you can just actually get that class and then do new AccessGroup() your life will probably be much easier.
One small change to what #Alex Wayne has answered above.
The original question had another property in AccessGroup called as access_group_features which is an array
declare class AccessGroup {
id: number;
active: boolean;
display_name: string;
description: string;
access_group_features: [];
created_at?: Date;
created_by?: string;
updated_at?: Date;
updated_by?: string;
static fromJSON(json: any): any;
toJSON(): this & {
access_group_features: any;
};
}
So the access_group property in toJSON() member function should also
be of the type array ( any other type throws an error as ampersand
operator in typescript intersects the properties and hence should be
of the same type)
toJSON(): this & {
access_group_features: []; /* this is probably because of strict type checking in typescript */
};
So the variable created would look something like this:
let x: AccessGroup = {
id: 123,
active: true,
display_name: 'Testing 123',
access_group_features:[],
toJSON() {
return {
...this,
toJSON: this.toJSON,
access_group_features: []
}
}
}
Hi I'd like to create a small authentication service in my angular application, I have a JSON file containing the information about my users:
{
"Users": [
{
"type":"teacher",
"id":0,
"name":"TEACHER ONE",
"login":"tone",
"password":"d450c5dbcc10db0749277efc32f15f9f"
},
{
"type":"student",
"id":1,
"name":"STUDENT ONE",
"login":"sone",
"password":"ec80dcc5a3eab73a4f128b66c1e4b92a"
},
{
"type":"student",
"id":2,
"name":"STUDENT TWO",
"login":"stwo",
"password":"62e73bf0deb1871860702b064106f1dc"
}
]
}
and here's what I try for my authentifiaction
import { Injectable } from '#angular/core';
import * as users from '../assets/Users.json';
import { clientType } from "./clientType";
#Injectable({
providedIn: 'root'
})
class user {
$type: clientType; //I put a dollar so equals won't compare this property
$id: number; //I put a dollar so equals won't compare this property
$name: string; //I put a dollar so equals won't compare this property
login: string;
password: string;
constructor(type: clientType, id: number, name: string, login: string, password: string) {
this.$type = type;
this.$id = id;
this.$name = name;
this.login = login;
this.password = password;
}
}
export class AuthService {
private userList: /*don't know what to put*/ = /*don't know what to put*/;
constructor() { }
consultData( ): void{
}
returnUsers() {
return this.userList;
}
}
I'm trying to get an array of users using my JSON file but I don't know how to extract the JSON object as user objects, can someone help me finding how ?
I am getting JSON object from API.
In service I have:
getUserInfo(token: string): Observable<IUser> {
return this.http.get<any>(this.apiUrl.getUser, {headers: {'X-Auth-Token': token}}).pipe(
tap(data => console.log(data)),
catchError(this.handleError)
);
}
In component:
this.authenticationService.getUserInfo(this.token).subscribe({
next: result => {
this.user = result;
console.log(this.user);
},
error: err => console.log(err)
})
This is my interface (simplified):
export class IUser {
username: string;
email: string;
role: string;
numberOfUsers: number;
expirationDate: string;
}
Is there a way to automatically map JSON from http request to that interface, so:
If property does not exist in JSON object from request, set it to
default value, for example numberOfUsers=0 or expirationDate = null.
If there is extra property in JSON object from request, just ignore
it.
Currently the user gets overwritten with values from JSON object from request.
Is there any automatic function of Objectable that would do this? Or I have to write that method in interface?
what you describe is a class, you can't have default values in an interface.
check class-transformer, it does what you want: https://github.com/typestack/class-transformer
It can convert a plain object to a class instance and respect default values and unrelated fields:
export class IUser {
username: string;
email: string;
role: string;
numberOfUsers: number = 0;
expirationDate: string = null;
}
this.user = plainToClass(IUser, result, {
excludeExtraneousValues: true, // <- ignores keys not for the class.
});
I have This JSON respond from my backend:
//User_Courses
[
{
id: 1,
name: "Ice King",
email: "pretty_princess1234#gmail.com"
completedCourses: [1,3],
unlockedCourses: [1,3,4,5,6],
completedLessons: [{"1" => [1,2,3]}, {"3" => [1,2,3,4,5,6,7]}, {"4" => [1]}]
},
{
id: 2,
name: "Mr. Crocker",
email: "fairy_godparents111#gmail.com"
completedCourses: [3],
unlockedCourses: [1,3,4],
completedLessons: [{"3" => [1,2,3,4,5,6,7]}, {"4" => [1,2]}]
}
]
// completed lessons are all the lesson the user finished.
// courses can be in progress or completed.
I want to fetch data from backend and subscribe it to this interface.
I don't sure how to implement the data structure and how to access data.
This is the interface I created:
export interface IUser {
id: number;
name: string;
email: string;
completedCourses: number[];
unlockedCourses: number[];
completedLessons: // <----- don't know what type to write
}
I want to know how to implement this, subscribe data with service and access data (in order to change it later and add data).
Thank you so much!
Create model for CompletedLesson (as mentioned in the comments):
interface ICompletedLesson {
[name: string]: number[];
}
interface IUser {
id: number;
name: string;
email: string;
completedCourses: number[];
unlockedCourses: number[];
completedLessons: ICompletedLesson[];
}
Then, create a service, something like this:
#Injectable()
export class UserService {
constructor(private http: HttpService) { }
fetchUserCourses(): Observable<IUser[]> {
return this.http.get<IUser[]>(`URL_TO_THE_USER_COURSES%);
}
}
And, wherever you are fetching data (some component for example):
fetchUserCourses() {
// userService is injected in this component's constructor
this.userService.fetchUserCourses().subscribe(users => {
// do something with result, yes, something like
this.users = users;
});
}
In the JSON you provided, to access the first lesson of the Mr. Crocker completed lessons (this.users are all users you retrieved from backend):
const firstCompletedLesson = this.users[1].completedLessons[0]; // {"3": [1,2,3,4,5,6,7]}
const lessons = firstCompletedLesson["3"]; // [1,2,3,4,5,6,7]
const firstLesson = lessons[0]; // 1
Furhermore, you can access "3" like this:
Object.keys(firstCompletedLesson)[0]; // 3
and you can add to array using push:
lessons.push(8); // [1,2,3,4,5,6,7,8]
and to add new completed lesson use:
this.users[1].completedLessons.push({ "5": [1, 2, 3] });
Hope this helps.
I have an interface IPost and a class Post, I want to create Post from json data, in Post constructor I get the json response where the interface IPost matches it.
I mean IPost interface is generated by this great tool json2ts
json2ts : generate TypeScript interfaces from JSON
import { IPost, Title, Guid, Content, Excerpt, Embedded, Links } from './Ipost.ts';
export class Post implements IPost {
Id: number;
Date: string;
DateGmt: string;
Guid: Guid;
Modified: string;
ModifiedGmt: string;
Slug: string;
Type: string;
Link: string;
Title: Title;
Content: Content;
Excerpt: Excerpt;
Author: number;
FeaturedImage: number;
CommentStatus: string;
PingStatus: string;
Sticky: boolean;
Format: string;
Links: Links;
Embedded: Embedded;
constructor(json: any) {
var self = this;
json.subscribe(res => {
var jsonRes: any = res.json();
self = jsonRes; //something like this
});
}
}
Can I assign the class Post to json directly since json is described the same as Post class!
Is there any other ways than assigning each property to its peer from json?
Just as you would JavaScript you'll have to iterate over each value, and assign that value to self, using standard loop. Assigning to self in your case, simply changes the value of self to the json value, it doesn't make any changes this itself.
json.subscribe(res => {
let json = res.json();
for (var prop in obj) {
if( obj.hasOwnProperty( prop ) ) {
this[prop] = obj[prop];
}
}
});
NOTE: => binds this to the outer context (for example the class you're working with) to this, so that you do not need to the intermediate self variable.