Typescript (Angular) - JSON model Deserialisation - json

I would like to add and populate additional fields (which are not sent by backend service) in my http model. Catch is that I am not able to populate (map) those fields in the place where http response is being received since I am using internal framework.
Is there a possibility in Typescript (Angular) to somehow override JSON Deserialisation flow/Instance creation and populate mentioned fields. For example:
interface ElectricDevice {
energy_meter_start: number; // received from backend service
energy_meter_stop: number; // received from backend service
energy_spent: number; // not received by backend service, but needs to be populated as energy_meter_stop - energy_meter_start
// ...
/* I would like to somehow populate energy_spent as energy_meter_stop-energy_meter_end on instance creation (deserialisation) */
}

You need a HttpInterceptor, in which you can manipulate data.
#Injectable()
export class CustomJsonInterceptor implements HttpInterceptor {
constructor(private jsonParser: JsonParser) {}
intercept(httpRequest: HttpRequest<any>, next: HttpHandler) {
if (httpRequest.responseType === 'json') {
// If the expected response type is JSON then handle it here.
return this.handleJsonResponse(httpRequest, next);
} else {
return next.handle(httpRequest);
}
}
Read more about it in the tutorials: https://angular.io/api/common/http/HttpInterceptor

I have asked you for the especific names of your services.
But, in the meantime, I give you a 'general' answer to your question.
You just need to do this:
this.yourService.yourGetElectriceDevices
.pipe(
map (_resp: ElectricDevice => _resp.energy_spent = _resp.energy_meter_stop - _resp.energy_meter_start
)
.subscribe( resp => { //your treatment to the response });
This above, only works for a rapid test.
If you want to do somethig more 'elaborated', you could transform your interface into a class, and add your calculated attribute, something like this:
export interface ElectricDevice {
energy_meter_start: number; // received from backend service
energy_meter_stop: number; // received from backend service
}
export Class ElectricDeviceClass {
energy_meter_start: number;
energy_meter_stop: number;
energy_spent: number;
constructor (data: ElectricDevice) {
this.energy_meter_start = data.energy_meter_start;
this.energy_meter_stop= data.energy_meter_stop;
this.energy_spent = this.energy_meter_stop - this.energy_meter_start;
}
And for using it, just:
import { ElectricDeviceClass, ElectricDevice } from './../model/...' // There where you have yours interfaces and model classes
this.yourService.yourGetElectriceDevices
.pipe(
map (_resp: ElectricDevice => new ElectricDeviceClass(_resp)
)
.subscribe( resp => { //your treatment to the response });

Related

In Angular 2, how do you intercept and parse Infinity / NaN bare-words in JSON responses?

I am writing an Angular front end for an API that occasionally serves Infinity and -Infinity (as bare words) in the JSON response object. This is of course not compliant with the spec, but is handled a few different JSON libraries, albeit optionally. I have an Angular service in place that can successfully retrieve and handle any retrieved entity that does not have these non-conforming values. Additionally, I have managed to get an HttpInterceptor in place which just logs when events trickle through, just to be sure I have it connected properly.
The issue that I am facing is that the HttpInterceptor seems to allow me to do one of two things:
Catch/mutate the request before it is sent to the API, or
Catch/mutate the request after it comes back from the API, and also after it is parsed.
What I would like to do is very similar to this question for native javascript, but I have not been able to determine if it is possible to tie into the replacer function of JSON.parse in the Angular Observable pipe (I think that if tying into that is possible it would solve my issue).
I have also found this question for Angular which is close, but they appear to have been able to handle changing the response to something other than the bare-words, which I don't have the liberty of doing.
This is the current implementation of my HttpInterceptor, note that it does not actually make any changes to the body. When retrieving an entity without these bare-word values, it logs to the console and all is well. When retrieving an entity with any of these bare-word values, an error is thrown before the HERE line is hit.
function replaceInfinity(body: string): string {
// Do something
return body;
}
#Injectable()
export class JsonInfinityTranslator implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map((event) => {
if (event instanceof HttpResponse) {
console.log("HERE");
return event.clone({body: replaceInfinity(event.body)});
} else {
return event;
}
})
);
}
}
TL;DR: Is there a way to mutate the body text of the returned response before the Angular built in JSON deserialization?
I was able to figure out how to achieve this, and it came down to:
Modifying the request to return as text instead of json
Catch the text response and replace the bare word symbols with specific string flags
Parse the text into an object using JSON.parse, providing a reviver function to replace the specific string flags with the javascript version of +/-Infinity and NaN
Here's the Angular HttpInterceptor I came up with:
import {Injectable} from '#angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '#angular/common/http';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
#Injectable()
export class JsonBareWordNumericSymbolTranslator implements HttpInterceptor {
private static infinityFlag = '__INFINITY_FLAG__';
private static negInfinityFlag = '__NEG_INFINITY_FLAG__';
private static nanFlag = '__NAN_FLAG__';
private static replaceBareWordSymbolsWithFlags(body: string): string {
const infinityBareWordPattern = /(": )Infinity(,?)/;
const negInfinityBareWordPattern = /(": )-Infinity(,?)/;
const nanBareWordPattern = /(": )NaN(,?)/;
return body
.replace(infinityBareWordPattern, `$1"${this.infinityFlag}"$2`)
.replace(negInfinityBareWordPattern, `$1"${this.negInfinityFlag}"$2`)
.replace(nanBareWordPattern, `$1"${this.nanFlag}"$2`);
}
private static translateJsonWithFlags(substitutedBody: string): any {
return JSON.parse(substitutedBody, (key: string, value: string) => {
if (value === this.infinityFlag) {
return Infinity;
} else if (value === this.negInfinityFlag) {
return -Infinity;
} else if (value === this.nanFlag) {
return NaN;
} else {
return value;
}
});
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.responseType !== 'json') {
// Do not modify requests with response types other than json
return next.handle(req);
}
return next.handle(req.clone({responseType: 'text'})).pipe(
map((event) => {
if (!(event instanceof HttpResponse)) {
return event;
}
const substitutedBody = JsonBareWordNumericSymbolTranslator.replaceBareWordSymbolsWithFlags(event.body);
const parsedJson = JsonBareWordNumericSymbolTranslator.translateJsonWithFlags(substitutedBody);
return event.clone({body: parsedJson});
})
);
}
}

How to catch a property of a JSON object from an Angular HTTP call and fix the "No property" error in Angular CLI?

I am using a public API to fetch movie data. And the following is my service method for getting that data from API:
getMovieList(): Observable<Movie[]> {
return this.http.get(this.moviesURL).pipe(
map((data: Movie[]) => data),
catchError(this.handleError),
);
}
And this is the method in the component for subscribing that data:
getMovieList(): void {
this.movieApiService.getMovieList()
.subscribe(
(data: Movie[]) => {
this.movieList = data.results;
}
);
}
The problem is that the API returns an object which has 4 properties: page, results, total_pages, total_results. And I only need the results property. But when I try to assign data.results to my component's property or send data.results from my service method instead of data then angular cli gives an error of "results is an undefined property of data". My question is how do I get the results property directly without having to touch the data object and i also need to assign Movie[] type to the results. But currently I am setting the type to the data object.
The problem lies in your model, you defined that you expect array of Movies but you receive the object with 4 properties which one of them called results are the model you defined, so the solution is:
Define the interface like this:
export interface IDataFromApi {
results: Movie[];
page: number;
total_pages: number;
total_results: number;
}
Then the first function will be:
getMovieList(): Observable<IDataFromApi> {
return this.http.get(this.moviesURL).pipe(
map((data: IDataFromApi) => data),
catchError(this.handleError),
);
And method in component:
getMovieList(): void {
this.movieApiService.getMovieList()
.subscribe(
(data: IDataFromApi) => {
this.movieList = data.results;
}
);
}

Angular array remains undefined when passing JSON data

I have an API which returns JSON data about football. The data is then passed to the frontend (angular) but when passing it to the array, the array is still remaining undefined.
JSON Data:
match_id":"194200",
"country_id":"41",
"country_name":"England",
"league_id":"148",
"league_name":"Premier League",
"match_date":"2019-04-01",
"match_status":"Finished",
"match_time":"21:00",
"match_hometeam_id":"2617",
"match_hometeam_name":"Arsenal",
"match_hometeam_score":"2 ",
"match_awayteam_name":"Newcastle",
"match_awayteam_id":"2630",
"match_awayteam_score":" 0",
This is the angular code to parse the JSON data and put in the array to display:
export class ResultComponent implements OnInit {
searchFilter: string;
resultArr: FootballModel[];
constructor(private footballService: FootballService, private route:
ActivatedRoute) {}
ngOnInit() {
this.footballService.getResults().subscribe(x => this.resultArr = x);
console.log(this.resultArr);
}
When I console.log the x passed in subscribe, the JSON information is returned. So till the x part it is passing well but when it is passing to resultArray and console.log that part, it is returning undefined. Wonder if anyone can help.
This is the model:
export class FootballModel {
countryName: string;
leagueName: string;
matchDate: string;
matchHomeTeamName: string;
matchAwayTeamName: string;
matchHomeTeamScore: string;
matchAwayTeamScore: string;
}
EDIT:
Also I am trying to display that data in a table, but somehow it is not showing. Pretty sure it's an easy mistake as well.
<tbody>
<tr *ngFor="let result of results">
<td>{{result.countryName}}</td>
<td>{{result.leagueName}}</td>
<td>{{result.matchDate}}</td>
<td>{{result.homeTeam}}</td>
<td>{{result.awayTeam}}</td>
<td>{{result.homeTeamScore}}</td>
<td>{{result.awayTeamScore}}</td>
</tr>
Http requests return Observable on Angular. Observables has async callback function and you can get data by subscribing it as you did. But when you try to reach data outside of callback function before .subscribe worked at least one time it must be undefined. Because it is writing to the console before your API send response. If you change your ngOnInit function like that it must work.
ngOnInit() {
this.footballService.getResults().subscribe(x => {
this.resultArr = x;
console.log(this.resultArr);
});
}
Also check the documentation for Observables
Here is an additional example for this case:
ngOnInit() {
console.log("a");
this.footballService.getResults().subscribe(x => {
console.log("c");
this.resultArr = x;
console.log(this.resultArr);
});
console.log("b");
}
Expected result on console is
"a" "b" "c"

How to map a JSON string into a TypeScript (JavaScript) object in AngularJS 2?

Consider this simple snippet of an AngularJS 2 application:
TestObject
export class TestObject {
id: number;
name: string;
}
TestService
[...]
export class TestService {
constructor(private http: Http) {}
test(): Observable<TestObject> {
return this.http
.get("http://www.example.com")
.map(this.save)
.catch(this.fail);
}
private save(response: Response) {
let testObject: TestObject = <TestObject> response.json();
return testObject || {};
}
private fail(error: any) {
return Observable.throw("error!");
}
}
AppComponent
[...]
export class AppComponent implements OnInit {
testObject: TestObject;
constructor(private testService: testService) {}
ngOnInit() {
this.testService.test().subscribe(
data => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
this.testObject = data;
console.log(this.testObject); // prints object, not TestObject?
},
error => { }
);
}
}
Here my questions:
1) Why does my application print out (using Chrome Inspector) object and not TestObject as type?
2) The property testObject of class AppComponent should be of type TestObject. Why does my application not fail?
3) How can I achieve that I really get TestObject? What would be the best way to do it? Of course I could just manually fill up my TestObject, but I hoped there is some way of automatically mapping the json to my object.
Here is an answer that I wrote to a question which explained the handling of observables in angular2.
Angular 2 http post is returning 200 but no response is returned
Here you can see how I am handling the Response object as returned by the service. It is very important that you return your response object from the map function in service.
Similarly you can convert your response object to typescript type by casting your response object. The example can be:
this._loginService.login(this.username, this.password)
.subscribe(
(response) => {
//Here you can map the response to a type.
this.apiResult = <IUser>response.json();
//You cannot log your object here. Here you can only map.
},
(err) => {
//Here you can catch the error
},
() => {
//this is fired after the api requeest is completed.
//here you can log your object.
console.log(this.apiResult);
//result will only be shown here.
}
);
Here, it can be clearly seen that I am casting the response object to IUser type.
Another thing is while handling apiresponse in your component it is to be noted that the subscribe function has three arguments and if you will like to log your object, you must do it in the last function of subscribe.
Hope this helps!
your call must be like
ngOnInit() {
this.testService.test().subscribe(
(data) => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
//only mapping
this.testObject = data;
},
error => { },
() => {
console.log(this.testObject);
}
);
}

Ionic2 and get Json

I am trying to use Ionic2 and I made a service to fetch a local stored Json.
import {Injectable} from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Rx';
import 'rxjs/add/operator/map';
#Injectable()
export class Page1Service {
public constructor(private _http: Http) {}
public GetItems() {
return this._http.get('/app/Ressources/Items.json').map((response: Response) => response.json().data);
}
public PrintJson():boolean {
var myresult;
this.GetItems().subscribe((result) => {
myresult = result;
console.log(result);
});
}
I also a made PrintJson() method that just print the json for test purpose.I got the error:
GET http://localhost:8100/app/Ressources/slides.json 404 (Not Found)
I don't get why. And I can't find an easy and uptodate tutorial. Or should I use fetch()?
First copy your json to the following dir(you can create the folder "data"):
[appname]/www/data/data.json
Type in the following command in your console:
ionic g provider JsonData
It should create a provider for you.Go to that page and enter the following in load() function:
load() {
if (this.data) {
// already loaded data
return Promise.resolve(this.data);
}
// don't have the data yet
return new Promise(resolve => {
// We're using Angular Http provider to request the data,
// then on the response it'll map the JSON data to a parsed JS object.
// Next we process the data and resolve the promise with the new data.
this.http.get('data/data.json').subscribe(res => {
// we've got back the raw data, now generate the core schedule data
// and save the data for later reference
this.data = res.json();
resolve(this.data);
console.log(this.data);
});
});
}
I usually create an Observable wrapped around the api-call like this:
public GetItems() {
return Observable.create(observer => {
this._http.get('/app/Ressources/Items.json').map(res =>res.json()).subscribe(data=>{
observer.next(data)
observer.complete();
});
});
}
Then I have to subscribe on that method in order to get the results and do something with it. (You could be to delegate the result to a list in the GUI)
GetItems().subscribe(data=>{
myResult = data;
});
EDIT: It might help to put this in the class as well
export class MyClass{
static get parameters(){
return [[Http]];
}
}
Just try to get the response.json() rather than response.json().data in GetItems() method
The issue is because of different paths of json files in local browser(computer) and device (android). Create data folder inside the src\assets folder. Move your json file into that.
When we run ionic serve, it will move that folder (with file) into www\assets folder. Then do following things:
Import Platform service of ionic2
import { Platform } from 'ionic-angular';
Inject Platform Service.
constructor(private http: Http, private platform: Platform ) { }
Use Platform Service.
public getItems() {
var url = 'assets/data/Items.json';
if (this.platform.is('cordova') && this.platform.is('android')) {
url = "/android_asset/www/" + url;
}
return this.http.get(url)
.map((res) => {
return res.json()
});
}