typescript - cast json to an interface - json

I have an interface like this:
export default interface IProject extends{
Id?:number;
name?:string;
description?:string;
}
and I when get data from the server the json file includes more properties like this:
{
id,
name,
description,
url,
startDate,
finishDate
}
but I only need the id, name and description fields. I tried this:
response.data.map((p: any) => p as IProject);
but the object includes the unnecessary data like url, startdate and finishDate
how can I map them correctly?
I know that we can map them like this:
response.data.map((p: any) => {
return {id:p.id,name:p.name,description:p.description}
});
but is there any other better ways to do that?

I'd recommend doing what you're doing, but additionally adding some types for your server response as well. That way you get some intellisense for your mapping functions.
interface IProject {
id?: number;
name?: string;
description?: string;
}
interface IProjectResponse {
id?: number;
name?: string;
description?: string;
url?: string;
startDate?: string;
finishDate?: string;
}
const mapResponse = (response: IProjectResponse[]) => response.data.map((p) => ({
id: p.id,
name:p.name,
description: p.description,
}));
const response = await fetch(/* .. */);
const data = await response.json();
const projects: IProject[] = mapResponse(data);

Related

In Angular How do I map a JSON response to a model where the model's and JSON's properties don't match?

My model looks like this:
export interface IFlightPlan {
flightPlanId: string;
aircraftIdentification: string;
aircraftType: string;
airspeed: number;
altitude: number;
flightType: string;
fuelHours: number;
fuelMinutes: number;
departureTime: Date;
arrivalTime: Date;
departureAirport: string;
arrivalAirport: string;
route: string;
remarks: string;
numberOnBoard: string;
}
And a sample of the JSON response from my API looks like this. The API returns an array of 25 of these:
{
"flight_plan_id":"f1d193ad0153491f9cf61cbe39c7db70",
"aircraft_identification":"N67SVS",
"aircraft_type":"PA-34 Piper Seneca",
"airspeed":128,
"altitude":12000,
"flight_type":"VFR",
"fuel_hours":3,
"fuel_minutes":41,
"departure_time":"2022-07-08T00:26:45Z",
"estimated_arrival_time":"2022-07-08T03:49:45Z",
"departing_airport":"KBXA",
"arrival_airport":"KNZY",
"route":"KBXA JOH J46 DMDUP J46 KNZY",
"remarks":"",
"number_onboard":4
}
As you can see, the properties of my model and those of the JSON response differ in casing and spelling.
Here's my Angular response service:
#Injectable({
providedIn: 'root'
})
export class FlightplansService {
private apiUrl = "https://localhost:3001/api/v1/flightplan";
constructor(private http: HttpClient) { }
getFlightPlans(): Observable<IFlightPlan[]> {
return this.http.get<IFlightPlan[]>(this.apiUrl).pipe(
tap(data => console.log('ALL FLIGHT PLANS: ', JSON.stringify(data))),
catchError(this.handleError)
);
}
handleError(err: HttpErrorResponse) {
let errorMessage = '';
errorMessage = `An error has occurred: ${err.error.message}`;
console.log(errorMessage);
return throwError(() => errorMessage);
}
}
and here's where I'm using it in my component
ngOnInit(): void {
this.flightPlanService.getFlightPlans().subscribe({
next: (data => this.flightPlans = data),
error: (err => this.errorMessage = err)
})
I can't access any of this.flightPlans[] children's properties. I don't quite understand what's going on by my guess is that this.flightPlans[] is assigned a generic array instead of IFlightPlan[] array because there's no way to map from the JSON to the model
How do I fix this?
Use map inside the pipe and define a result object as IFlightPlan as flowing:
getFlightPlans(): Observable<IFlightPlan[]> {
return this.http.get<IFlightPlan[]>(this.apiUrl).pipe(
map(flightPlans => flightPlans.map(item =>
{
let flightPlan: IFlightPlan = {
flightPlanId: item.flight_plan_id,
aircraftIdentification: item.aircraft_identification,
...
}
return flightPlan;
}
)),
tap(data => console.log('ALL FLIGHT PLANS: ', JSON.stringify(data))),
catchError(this.handleError)
);
}
UPDATE:
While you'll map the incoming data after reading so it's enough to make http.get read the data as array of any as flowing:
getFlightPlans(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl).pipe(
map((flightPlans) =>
flightPlans.map((item) => {
let flightPlan: IFlightPlan = {
flightPlanId: item.flight_plan_id,
aircraftIdentification: item.aircraft_identification,
..
};
return flightPlan;
})
),
tap((data) => console.log('ALL FLIGHT PLANS: ', JSON.stringify(data))),
catchError(this.handleError)
);
}
Don't forget to fill the right fields names from item

Angular Map object from Get to Interface

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

TypeORM bulk create with relations

Is there a way to insert large amount of datas without blowing the JS heap memory ? I have a model which is Email as follow :
#Entity("email")
export class Email extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#ManyToOne((type) => Category, (cat) => cat.category, {nullable: false, cascade: ['insert']})
public category: Category;
#Column({type: "text", name: "email"})
public email: string;
}
and Category :
#Entity("category")
export class Category extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#Column({type: "text", name: "category"})
public category: string;
#OneToMany((type) => Email, (email) => email.category, {nullable: true})
public emails: Email[];
}
First problem I had is when I try to save {email: 'blabal#blalbah.com', category: 'default'} it says that Category must be an ID, but the thing is I want to add emails and create the category if it doesnt exist or asign the ID to the email if it exists. I did the following code :
public async bulkCreate(emails: Email[]): Promise<any> {
try {
const emailRepo = await getRepository(Email);
const categoryRepo = await getRepository(Category);
await Promise.all(emails.map(async (mail) => {
const cat = await categoryRepo.findOne({where: {category: mail.category}});
if (cat) {
// #ts-ignore
mail.category = cat.id;
} else {
const newCat = await categoryRepo.save(Object.assign(new Category(), mail));
// #ts-ignore
mail.category = newCat.id;
}
await emailRepo.save(mail);
}));
} catch (e) {
console.log(e);
throw new Error(e);
}
}
Worked for a few emails, but when I try to add even only 1,000 memory goes up to Like 4Gig and just crash.
What should I do? I'd like to add more than 1,000 emails at once.
I know it's little bit late, but solution for this use Bluebird Promise.map so you can define concurrency. instead executing in one run.

Typescript object from JSON

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.

Typescript create a class from json

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.