JSON data fails to map to my exported interface? - json

I can't bind my Angular HTML to my devices array because there is a mismatch between what I am expecting the object to look like. I want to bind to device.modelName but it fails because the object I'm actually getting back has the property ModelName without camelCase!
Googling a solution I saw that I should use type assertion so I did by adding <DeviceStatus[]> resulting in the line <DeviceStatus[]>response.json()); in my component class. Unfortunately this didn't seem to change anything.
Why does it seem like my json object isn't being mapped properly?
My model interface:
export interface DeviceStatus {
deviceId: string;
modelName: string;
}
My component:
export class FetchDataComponent {
public devices: Observable<DeviceStatus[]>;
constructor(http: Http) {
this.devices =
http.get('api/Devices')
.map((response: Response) => <DeviceStatus[]>response.json());
this.devices.subscribe(x => console.log(x));
}
}
The result in Chrome console:
(2) [Object, Object]
0: Object
DeviceId: "1"
ModelName: "Model Name"
...

Type assertion only informs the compiler, it does nothing to the actual data as it says in the docs:
A type assertion is like a type cast in other languages, but performs
no special checking or restructuring of data
Always remember that the type system in typescript doesn't get translated to javascript, so in runtime you don't have that.
You'll need to do the convertion yourself:
this.devices =
http.get('api/Devices')
.map((response: Response) => response.json().map(device => ({
deviceId: device.DeviceId,
modelName: device.ModelName
}));

Here is the ASP.NET code using the formatter:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver=
new CamelCasePropertyNamesContractResolver();
It converts the properties to camel case before returning them in the response.
You can find out more about it here: http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_CamelCasePropertyNamesContractResolver.htm
And there is a more complete example in this post here: Web API 2: how to return JSON with camelCased property names, on objects and their sub-objects

Related

Mapping response of an angular http client get request to Typescript class objects

I am using a Service in angular to retrieve JSON responses from a path as given below.
const request = this.http.get<ShoppingCartItem>('assets/json/shopping-cart-test.json');
The above JSON file in the assets folder has an array of JSON objects. I want to convert the response to an array of ShoppingCartItem objects. Following given is the ShoppingCartItem class that I want to map. How to achieve this?
export class ShoppingCartItem {
$key: string;
title: string;
imageUrl: string;
price: number;
quantity: number;
constructor(init?: Partial<ShoppingCartItem>) {
Object.assign(this, init);
}
get totalPrice() { return this.price * this.quantity; }
}
I am not familiar with handling the HTTP response with methods like subscribe, pipe and map. A detailed explanation would be more helpful.
You already had the fundamentals right.
Inside an Angular.component ts file,
We first create a request with final type you expect from the request i.e - ShoppingCartItem similar to what you have done in your code
const shoppingCartItem$: Observable<ShoppingCartItem> = http.get<ShoppingCartItem>('/assets/db.json');
Notice the $sign on shoppingCartItem$, usually used with an Observable, the return type when you make HTTP Requests in Angular.
The subscribing part is done after declaring our Observable in this case. Subscribe method is used to make HTTP Requests, without it no requests are made to the server.
Pipe and map are RXJS operators that allows us to make changes to response. It basically gives you access to data in your observable so you can make changes. They are usually used in conjunction.
In your case, it could be used like
this.shoppingCartItem$:.pipe(
map((items: shoppingCartItem[]) => items.map(items=> items.key))
)
Working Stackblitz is below.
Local JSON subscription example

Best approach to deal with JSON responses in the Front End/Angular when subscribing it as a forkJoin?

I have read multiple answers to these kind of issues, and each answer has its own response;
In my case I am not getting any of those as my interfaces simply don't map the json like I want it to. I have tried multiple solutions, since working with Root-object and nested interfaces, but here I am, asking which is the best approach to deal with these kind of JSON objects in the front end, how to map it this particular one (a fork-Join). and I wanted to ask what are the real benefits of using the interfaces/classes/ maps besides the Intellisense? It has to do with data propagation?
The json structure in question:
{
Title: "",
Year: "",
Rated: "",
Released: "",
Runtime: "", 
…}
Simple as it is. But back in my service I call it with a forkjoin:
getMovies(name: string, year?: string): Observable<any> {
let shortPlot = this.http.get(
"https://www.omdbapi.com/?t=" +
name +
"&plot=short&y=" +
year +
"&apikey=[my key]"
);
let fullPlot = this.http.get(
"https://www.omdbapi.com/?t=" + name + "&plot=full&apikey=[my key]"
);
return forkJoin([shortPlot, fullPlot]);
}
The subscription in the component:
getMovie() {
this.spinner = true;
this.movieService
.getMovies(this.name.value)
.subscribe((dataList: any) => {
this.movies = Array.of(dataList[0]);
this.spinner = false;
let error: any = this.movies.map(error => error.Error);
if (error[0]) {
this.notfound = error[0];
this.error = true;
} else {
this.error = false;
this.movieRate = this.movies.map(rating => rating.imdbRating.toString());
}
})),
error => console.log(error);
}
And in the HTML I render the data like this:
<div *ngFor="let m of movies">
<h5 class="mt-0">{{m.Title}}, {{m.Year}}</h5>
</div>
So as you can see I am not working with an interface and I should. Anyone can sort me out?
Thank you
EDIT: the log after subscribe:
let's break it down,
what are the real benefits of using the interfaces/classes/ maps besides the Intellisense?
Using interfaces and classes will not just give you intellisense but will also provide static type safety for your code. Why this is important, let's say you have a interface with following structure,
export interface Demo {
field: string;
}
// in some other file 1
demo.field.substring(1, 2);
// in some other file 2
demo.field.lenght;
You are using this interface in many places in your code. Now, for some reason you get to know that the property should be number not string. So here typescript will give you all the errors at compile time only.
export interface Demo {
field: number;
}
// in some other file 1
demo.field.substring(1, 2); // error
// in some other file 2
demo.field.lenght // error
Also, after typescript transpiles it will generate javascript files, now as javascript is interpreted language, your code will not be tested until the javascript run-time actually executes the problematic line, but in typescript you will get errors in compilation stage only.
You can get away with using any everywhere, but with that you will be missing the static typings.
With interfaces and classes, you also get OOP features, such as inheritance etc.
It has to do with data propagation?
Your frond-end is never aware what type of data will be received from api. So it's developers responsibility that the received data should be mapped to some interface.
Again as mentioned above, if somehow back-end changes type of some field in received json, then it will again be caught in compile time.
In case of forkJoin which combines output of two jsons you can have two different types.
Demo
export interface Demo1 {
field1: string;
}
export interface Demo2 {
field2: number;
}
// in service layer
getData(): Observable<[Demo1, Demo2]> {
const res1 = this.http.get(...);
const res2 = this.http.get(...);
return forkJoin([res1, res2]);
}
// in component
this.dataService.getData().subscribe(res => {
// you will get type safety and intellisense for res here
console.log(res[0].field1)
})
I am not working with an interface and I should.
Yes, you should use interfaces, if you are not using using features of typescript then whats the point using it. :)

How to cast fetch API response to a specific type in TypeScript?

I'm trying to cast the response of an API call into a specific type in my code (TypeScript). However, I didn't succeed to get the response casted to my type, but only Object.
I tried several ways, in particular what is described here: https://www.carlrippon.com/fetch-with-async-await-and-typescript/
Type:
export interface MyType{
id: number;
name: string;
date: Date;
}
Fetch code:
export default class api {
static async get<T>(url: string): Promise<T> {
return new Promise(resolve => {
fetch(url)
.then(response => response.json())
.then(body => {
resolve(body);
});
});
}
}
Caller:
const result = await api.get<MyType[]>(
"my-url-here"
);
I expect result to be of type MyType[], but it is Object[]. (with all the fields)
Further, I tried to change the type of the name field to number (while always keeping the same response from API, i.e. name is some string non-convertible to number) and expected some type casting error, but there was none. result is still created with the same content and with the same type:
result: Array(3) [Object, Object, Object]
It seems that there isn't any casting at all.
Can you please tell me where I'm wrong at, and show me what to do?
Thank you very much.
expected some type casting error, but there was none.
TypeScript doesn't cast. It asserts. It is a way for you to focefully tell the compiler what something will be at runtime. If it doesn't turn out to be that at runtime its on you.
Fix
Options, either:
change the backend response so it matches what you are asserting.
change the assertion to match what the backend is returning.
More
TypeScript Assertion

TypeScript / Angular 2 creating a dynamic object deserializer

So I am coming from a background of C# where I can do things in a dynamic and reflective way and I am trying to apply that to a TypeScript class I am working on writing.
Some background, I am converting an application to a web app and the backend developer doesn't want to change the backend at all to accommodate Json very well. So he is going to be sending me back Json that looks like so:
{
Columns: [
{
"ColumnName": "ClientPK",
"Label": "Client",
"DataType": "int",
"Length": 0,
"AllowNull": true,
"Format": "",
"IsReadOnly": true,
"IsDateOnly": null
}
],
Rows:[
0
]
}
I am looking to write an Angular class that extends Response that will have a special method called JsonMinimal which will understand this data and return an object for me.
import { Response } from "#angular/http";
export class ServerSource
{
SourceName: string;
MoreItems: boolean;
Error: string;
ExtendedProperties: ExtendedProperty[];
Columns: Column[];
}
export class ServerSourceResponse extends Response
{
JsonMinimal() : any
{
return null; //Something that will be a blank any type that when returned I can perform `object as FinalObject` syntax
}
}
I know StackOverflow isn't for asking for complete solutions to problems so I am only asking what is one example taking this example data and creating a dynamic response that TypeScript isn't going to yell at me for. I don't know what to do here, this developer has thousands of server-side methods and all of them return strings, in the form of a JSON or XML output. I am basically looking for a way to take his column data and combine it with the proper row data and then have a bigger object that holds a bunch of these combined object.
A usage case here after that data has been mapped to a basic object would be something like this.
Example:
var data = result.JsonMinimal() as LoginResponse; <-- Which will map to this object correctly if all the data is there in a base object.
var pk = data.ClientPK.Value;
I'm not exactly sure I understand, but you may want to try a simple approach first. Angular's http get method returns an observable that can automatically map the response to an object or an array of objects. It is also powerful enough to perform some custom mapping/transformation. You may want to look at that first.
Here is an example:
getProducts(): Observable<IProduct[]> {
return this._http.get(this._productUrl)
.map((response: Response) => <IProduct[]> response.json())
.do(data => console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
Here I'm mapping a json response to an array of Product objects I've defined with an IProduct interface. Since this is just a "lambda" type function, I could add any amount of code here to transform data.

Getting Type Error when using Ember Embedded Records Mixin with keyForAttribute

I've been banging my head against deserializing data with Ember. I feel like I've set it up right but I keep getting the same error. I'm trying to use the EmbeddedRecords Mixin, but it simply hasn't worked for me. Below is my debug data.
DEBUG: Ember : 1.6.1
DEBUG: Ember Data : 1.0.0-beta.7+canary.b45e23ba
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.10.2
DEBUG: Model Fragments : 0.2.2
Here is a simple setup of what I've been doing. I have my model defined like this -
App.Subject = DS.Model.extend({
title: DS.attr('string'),
sections: DS.hasMany('section')
});
App.Section = DS.Model.extend({
title: DS.attr('string'),
subject: DS.belongsTo('subject')
});
App.SubjectSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
sections: { embedded: 'always' }
}
});
and here is the format of the JSON payload I'm sending for a 'show'
{
"subject": {
"_id":"549987b098909eef0ac2d691",
"title":"Maths",
"sections":[{
"title":"Precalc",
"_id":"549987b098909eef0ac2d693"
}, {
"title":"Calc",
"_id":"549987b098909eef0ac2d692"
}],"__v":0
}
}
I get the errors in the console
Error while processing route: subjects.show undefined is not a function TypeError: undefined is not a function
at Ember.Mixin.create.extractSingle (http://localhost:3300/js/highered.js:2043:25)
at apply (http://localhost:3300/js/highered.js:20664:27)
at superWrapper [as extractSingle] (http://localhost:3300/js/highered.js:20240:15)
at Ember.Object.extend.extractFind (http://localhost:3300/js/highered.js:4007:21)
at Ember.Object.extend.extract (http://localhost:3300/js/highered.js:3892:37)
at http://localhost:3300/js/highered.js:11864:34
at invokeCallback (http://localhost:3300/js/highered.js:23228:19)
at publish (http://localhost:3300/js/highered.js:22898:9)
at publishFulfillment (http://localhost:3300/js/highered.js:23318:7)
at http://localhost:3300/js/highered.js:28736:9
highered.js:16581 undefined is not a function TypeError: undefined is not a function
at Ember.Mixin.create.extractSingle (http://localhost:3300/js/highered.js:2043:25)
at apply (http://localhost:3300/js/highered.js:20664:27)
at superWrapper [as extractSingle] (http://localhost:3300/js/highered.js:20240:15)
at Ember.Object.extend.extractFind (http://localhost:3300/js/highered.js:4007:21)
at Ember.Object.extend.extract (http://localhost:3300/js/highered.js:3892:37)
at http://localhost:3300/js/highered.js:11864:34
at invokeCallback (http://localhost:3300/js/highered.js:23228:19)
at publish (http://localhost:3300/js/highered.js:22898:9)
at publishFulfillment (http://localhost:3300/js/highered.js:23318:7)
at http://localhost:3300/js/highered.js:28736:9
Which as best I can tell is directly related to extractSingle at the this.keyForAttribute method
extractSingle: function(store, primaryType, payload, recordId, requestType) {
var root = this.keyForAttribute(primaryType.typeKey),
partial = payload[root];
updatePayloadWithEmbedded(store, this, primaryType, partial, payload);
return this._super(store, primaryType, payload, recordId, requestType);
},
although an interesting thing to note is that the error occurs at extractArray when I am using the subjects index route, which return the json above but with array brackets as well.
extractArray: function(store, type, payload) {
var root = this.keyForAttribute(type.typeKey),
partials = payload[pluralize(root)];
forEach(partials, function(partial) {
updatePayloadWithEmbedded(store, this, type, partial, payload);
}, this);
return this._super(store, type, payload);
}
Which makes me think that Ember Data is having trouble recognizing the format. This happens any time I define a serializer for a model, not just when I enable embedded records.
I'm hoping someone will be able to explain this. As a final note I've been using the Ember Data Model Fragments library as well, but I disabled that and still got this error so I don't think that is it.
The Embedded Records mixin doesn't work with the RESTSerializer before beta 9.
You can view the state of it here Ember-data embedded records current state?
You'll also want to be wary of updating ember or ember data without the other version in certain circumstances. Ember Data cannot read property 'async' of undefined