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.
});
Related
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
I have a class with an interface :
from another component in the code I read a json of my list of hero, and want to create a class for each hero (so it is easier to manipulate their data)
MY shop list CLASS =>
interface ShopItemInterface {
name: string;
image: string;
price: number;
description: string;
}
export class ShopItem implements ShopItemInterface {
public name: string;
public image: string;
public price: number;
public description: string;
constructor(obj: object) {
for (let key in obj) {
// this doesn't work and I don't know why, it is always false
if (this.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
}
}
LOADER COMPONENT CLASS =>
ngOnInit() {
this.http.get(this.jsonShopLocationFile).subscribe(res => {
for (let i = 0; i < res['items'].length; i++) {
this.shopItems.push(new ShopItem(res['items'][i]));
}
console.log(this.shopItems[0].name);
});
}
I can't find a way to correctly bind the json data to an object without listing all the parameters manually. ( which would be a mess and with 0 reusability)
How would you achieve that correctly ? Should I create a class and then directly call a function like hero.FromJSON(jsonObj) to manually set all the property? can I do this in some way in the constructor ?
thank you !
Because when you are constructing the object it does not have those properties, they are going to be undefined, just remove the test and it will work. Remember that interfaces are a TypeScript construct for the compiler and that you are running JavaScipt in your browser.
for (let key in obj) {
this[key] = obj[key];
}
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);
I have the following code (based on the Angular2 Hero example) and I am trying to get a JSON API call (AWS) converted into a TypeScript class.
The code returns no errors, and I can see that the object is there when I look at response.json() but the class product remains undefined, any ideas?
Code in the service file...
getProduct(id: number): Promise<Product> {
const url = `${this.ProductsUrl}/${id}`;
console.log(url);
return this.http.get(url)
.toPromise()
.then(response => response.json().data as Product)
.catch(this.handleError);
}
Code in the component file...
ngOnInit(): void {
this.route.params
.switchMap((params: Params) => this.productsService.getProduct(+params['id']))
.subscribe(product => this.product = product);
}
The class...
export class Product {
Instock: boolean;
Description: string;
CategoryId: number;
Id: number;
ColourOptions: string[];
Name: string;
}
The JSON returned from the API call...
{
"Instock":true,
"Description":"This is a test description.",
"CategoryId":1,"
Id":1,
"ColourOptions":["Red","Gold","Green"],
"Name":"Test"
}
switchMap'callback must return an Observable not a Promise, so edit getProduct to return a Observable instead:
getProduct(id: number): Observable<Product> {
const url = `${this.ProductsUrl}/${id}`;
console.log(url);
return this.http.get(url)
.map(response => response.json().data as Product)
.catch(this.handleError);
}
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.