Typescript GET object with date type - json

I have python REST API and angular2 on frontend. Now I need to parse GET request to objects Answer with date type field. Example of my json:
{
"date_answer": "2015/07/05",
"id": 1,
"text": "some text",
}
I use this code in controller:
getAnswers() : Observable<Answer[]> {
return this.http.get(this.answerUrl)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));}
What I am trying to do is to solve my problem with parsing Date field in constructor like this:
export class Answer {
id: number;
text: string;
date_answer: Date;
constructor(
id: number,
text: string,
date_string: string
){
this.date_answer = new Date(date_string)
}
}
But this approach does't help. Maybe there are some better way to get date from json as Date type.

I'm using class-transformer by pleerock to achieve parsing.
Maybe you take a look at it.

Related

Type safe deserialization in TypeScript

I have this sample TypeScript code that is supposed to deserialize a simple JSON into an instance of class Person and then call foo method on it, but it doesn't work:
class Person {
name!: string;
age!: number;
foo() {
console.log("Hey!");
}
}
fetch("/api/data")
.then(response => {
return response.json() as Promise<Person>;
}).then((data) => {
console.log(data);
data.foo();
});
The output of console show that object is in a proper shape, but it is not recognized as Person:
Object { name: "Peter", age: 44 }
​
age: 44
​name: "Peter"
​
Thus when it tries to call foo method it fails:
Uncaught (in promise) TypeError: data.foo is not a function
http://127.0.0.1:8000/app.js:14
promise callback* http://127.0.0.1:8000/app.js:12
How can I fix it? Should I use Object.assign or there is another better/native solution?
let x = (<any>Object).assign(Object.create(Person.prototype), data);
x.foo();
Remember, TypeScript is just a way of annotating JavaScript code with type guards. It doesn't do anything extra. For example, saying that the object returned by response.json() should be treated as a Promise<Person> does not mean it will invoke the constructor of your Person class. Rather, you'll just be left with a plain old JavaScript object that has a name and an age.
It looks to me like you'll need to create a constructor for your Person class which can create a new instance of a Person based on an object that matches its interface. Something like this, perhaps?
interface PersonLike {
name: string;
age: string;
}
class Person implements PersonLike {
constructor(data: PersonLike) {
this.name = data.name;
this.age = data.age;
}
name: string;
age: string;
foo() {
console.log("Hey!");
}
}
fetch("/api/data")
.then(response => {
return response.json() as Promise<PersonLike>;
}).then((data) => {
const person = new Person(data);
person.foo();
});
I'd also recommend using a type guard instead of the as keyword, in case the API you're fetching data from changes. Something like this, perhaps:
function isPersonLike(data: any): data is PersonLike {
return typeof data?.name === 'string' && data?.age === 'string';
}
fetch("/api/data")
.then(response => {
return response.json();
}).then((data: unknown) => {
if (isPersonLike(data)) {
const person = new Person(data);
person.foo();
}
});
... is supposed to deserialize a simple JSON into an instance of class Person and then ...
Unfortunately, generic type in TypeScript only works as some kind of model design assistant. It will never be compiled into JavaScript file. Take your "fetch" code for example:
fetch("/api/data")
.then(response => {
return response.json() as Promise<Person>;
}).then((data) => {
console.log(data);
data.foo();
});
After compile the above TypeScript file into JavaScript, we can find the code as Promise<Person> is completely removed:
fetch("/api/data")
.then(function (response) {
return response.json();
}).then(function (data) {
console.log(data);
data.foo();
});
To implement "type safe deserialization", you need to save class/prototype information during serialization. Otherwise, these class/prototype information will be lost.
... or there is another better/native solution? ... BTW, what if a class field has a custom type, so it is an instance of another class?
No, there is no native solution, but you can implement "type safe" serialization/deserialization with some libraries.
I've made an npm module named esserializer to solve this problem automatically: save JavaScript class instance values during serialization, in plain JSON format, together with its class name information. Later on, during the deserialization stage (possibly in another process or on another machine), esserializer can recursively deserialize object instance, with all Class/Property/Method information retained, using the same class definition. For your "fetch" code case, it would look like:
// Node.js server side, serialization happens here.
const ESSerializer = require('esserializer');
router.get('/api/data', (req, res) => {
// ...
res.json(ESSerializer.serialize(anInstanceOfPerson));
});
// Client side, deserialization happens here.
const ESSerializer = require('esserializer');
fetch("/api/data")
.then(response => {
return response.text() as Promise<string>;
}).then((data) => {
const person = ESSerializer.deserialize(data, [Person, CustomType1, CustomType2]);
console.log(person);
person.foo();
});

Can't get Nested JSON Object Property Typescript

I want to get CAD value from https://api.exchangeratesapi.io/latest
I have already used so many types of code, and it says that "TypeError: Cannot read property 'CAD' of undefined"
Really need your help, thank you very much.
It outputted all the Currencies if I console this code
((this.state.data as any).rates)
but when i want to get CAD currencies, it says the error
I have tried these codes :
((this.state.data as any).rates as any).CAD
(this.state.data as any)["Rates"]["CAD"];
(this.state.data as any)["Rates"].CAD;
The way I get the data is
interface IState {
data?: object | null;
isCurrency?: boolean;
Currency?: string;
Rate?: number;
}
export default class Header extends Component<{}, IState> {
service: UserService = new UserService();
state = {
isCurrency: false,
Currency: "USD",
Rate: 1,
data: [] = []
};
async componentDidMount() {
let result = await this.service.getAllCurrency();
this.setState({
data: (result as Pick<IState, keyof IState>).data
});
console.log(result);
}
}
1.4591 (Based on the latest API)
You should create a type for your data. Because it's coming from an external source, typescript cannot infer that. Then parse your JSON and cast it to that type.
// Create a type for the expernal data.
interface Data {
rates: {
[currency: string]: number
}
base: string
date: string
}
// Result of `JSON.parse()` will be `any`, since typescript can't parse it.
const untypedData = JSON.parse(`{
"rates": {
"CAD": 1.4591,
"HKD": 8.6851,
"ISK": 135.9,
"PHP": 56.797,
"DKK": 7.4648
},
"base": "EUR",
"date": "2019-07-25"
}`)
// Cast the untyped JSON to the type you expect it to be.
const data: Data = untypedData
// Use the data according to it's type.
alert(data.rates.CAD)
Working demo on typescript playground

Angular Js parse deeper JSON file

I am trying to parse an array inside an object. I tried to map the result to get the array but could not reach to the point of the array.
My JSON looks like this.
{
"id": 1,
"projectName": "Opera house",
"projectDescription": "This image was taken during my first photography course.",
"thumbnailImageName": "1.JPG",
"projectDetails": {
"id": 1,
"relatedPhotos": [
"1.JPG",
"2.JPG",
"3.JPG"
],
"location": "Sydney",
"scope": "Learn basic of photography",
"description": "Some description"
},
"favouriteProject": true
}
And I am mapping the HTTP response from a server like this.
this.projectService.getProjectDetailsByProjectName(projectName).subscribe(res =>
{
Object.keys(res).map(key => {
this.projectDetails = res[key];
})
});
The above mapping gives me the projectDetails object but cannot access the array inside it. While accessing the array, I get output three times. Two times undefined and finally the actual value. Can anyone guide me how to parse the above JSON file properly?
Thank you very much..
************Edited code****************
My code to get the http response and parse each object is as follows:
getSelectedProjectWithDetails(){
const projectName:string = this.activatedRoute.snapshot.paramMap.get("project-name");
this.projectService.getProjectDetailsByProjectName(projectName).subscribe(res => {
// console.log(res.relatedPhotos);
Object.keys(res).map( (key, value) => {
this.projectDetails = res[key];
console.log(this.projectDetails["relatedPhotos"])
})
})
}
I have project interface as
export interface project{
id:number;
projectName: string;
projectDescription: string;
favouriteProject: boolean;
thumbnailImageName: string;
projectDetail: projectDetail;
}
and projectDetails interface as:
export interface projectDetail{
id: number;
relatedPhotos: String [];
location: string;
scope: string;
description: string;
}
and http get request is
getProjectDetailsByProjectName(projectName: String): Observable<project>{
return this.http.get<project>("http://127.0.0.1:8080/project/"+projectName);
}
As an alternate you can user JSON.parse(res); after you have mapped your response from observable.
like this
Object.keys(res).map(key => {
JSON.parse(res);
this.projectDetails = res[key];
})
However I am using res.json();
addNewProduct(data: any): Observable<string> {
return this._http.post(this.addNewProdUrl, data).map(res => res.json());
}
Not sure what is the issue with your res.
You can use map() to transform HttpResponse body to JSON using json() method. Since response contains body, headers etc. json() can be used to only parse body.
Please look into below code to understand the same.
this.http.get('https://api.github.com/users')
.map(response => response.json())
.subscribe(data => console.log(data));
To know more, please refer documentation

Deserializing json in Angular 2/4 using HttpClientModule

So I'm getting the following JSON structure from my asp.net core api:
{
"contentType": null,
"serializerSettings": null,
"statusCode": null,
"value": {
"productName": "Test",
"shortDescription": "Test 123",
"imageUri": "https://bla.com/bla",
"productCode": null,
"continuationToken": null
}
}
I have the following typescript function that invokes the API to get the above response:
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; //not working...
console.log("DATA: " + data);
console.log("DATA: " + data['value']);
return data;
});
}
ProductVM:
export interface ProductVM {
productName: string;
shortDescription: string;
imageUri: string;
productCode: string;
continuationToken: string;
}
My problem is that I can't deserialize it to ProductVM. The console logs just produce [object Object]
How can I actually map the contents of the value in my json response to a ProductVM object?
Is it wrong to say that data is a ProductVM in the map function? I have tried lots of different combinations but I cannot get it to work!
I'm unsure whether I can somehow automatically tell angular to map the value array in the json response to a ProductVM object or if I should provide a constructor to the ProductVM class (it's an interface right now), and extract the specific values in the json manually?
The data object in the map method chained to http is considered a Object typed object. This type does not have the value member that you need to access and therefore, the type checker is not happy with it.
Objects that are typed (that are not any) can only be assigned to untyped objects or objects of the exact same type. Here, your data is of type Object and cannot be assigned to another object of type ProductVM.
One solution to bypass type checking is to cast your data object to a any untyped object. This will allow access to any method or member just like plain old Javascript.
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: any) => this.externalProduct = data.value);
}
Another solution is to change your API so that data can deliver its content with data.json(). That way, you won't have to bypass type checking since the json() method returns an untyped value.
Be carefull though as your any object wil not have methods of the ProductVM if you ever add them in the future. You will need to manually create an instance with new ProductVM() and Object.assign on it to gain access to the methods.
From angular documentation: Typechecking http response
You have to set the type of returned data when using new httpClient ( since angular 4.3 ) => this.http.get<ProductVM>(...
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get<ProductVM>("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; // should be allowed by typescript now
return data;
});
}
thus typescript should leave you in peace
Have you tried to replace
this.externalProduct = data;
with
this.externalProduct = data.json();
Hope it helps
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map(data => {
this.externalProduct = <ProductVM>data;
console.log("DATA: " + this.externalProduct);
return data;
});
}
So, first we convert the response into a JSON.
I store it into response just to make it cleaner. Then, we have to navigate to value, because in your data value is the object that corresponds to ProductVM.
I would do it like this though:
Service
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get(`api/product?productCode=${code}`)
.map(data => <ProductVM>data)
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
Component
this.subscription = this.myService.getProductExternal(code).subscribe(
product => this.externalProduct = product,
error => console.warn(error)
);
I used this approach in a client which uses the method
HttpClient.get<GENERIC>(...).
Now it is working. Anyway, I do not understand, why I do not receive a type of T back from the http client, if I don't use the solution provided in the answer above.
Here is the client:
// get
get<T>(url: string, params?: [{key: string, value: string}]): Observable<T> {
var requestParams = new HttpParams()
if (params != undefined) {
for (var kvp of params) {
params.push(kvp);
}
}
return this.httpClient.get<T>(url, {
observe: 'body',
headers: this.authHeaders,
params: requestParams
}).pipe(
map(
res => <T>res
)
);
}

Angular: Typescript casting JSON response as object model not working

I have an issue while I try to cast a json response to object, all the properties of my object are string is that normal ?
Here is my ajax request :
public getSingle = (keys: any[]): Observable<Badge> => {
return this._http.get(this.actionUrl + this.getKeysUrl(keys))
.map((response: Response) => response.json() as Badge )
.catch(this.handleError);
}
Here is my badge model :
export interface Badge {
badgeNumber: number;
authorizationLevel: number;
endOfValidity: Date;
}
And here is where I call the service function and I'm facing the issue :
this._badgeService.getSingle(this.ids).subscribe(
(badge: Badge) => {
console.log(typeof(badge.endOfValidity)); // <-- returning string and not Date
},
error => console.log(error);
});
Thats kinda tricky to explain:
Date is a class, this means that values of type Date need to be created through a constructor call. In other words, create a class instance with new Date(...).
The Response.json method will only return an object in JSON format, and such doesnt contain an instance of any class, only maps of key:property.
So what you need to do, is to manually convert the value returned from .json() to a Base object. This can be done as follows:
public getSingle = (keys: any[]): Observable<Badge> => {
return this._http.get(this.actionUrl + this.getKeysUrl(keys))
.map(r => r.json())
.map(v => <Badge>{
badgeNumber: v.badgeNumber,
authorizationLevel: v.authorizationLevel,
endOfValidity: new Date(v.endOfValidity)
// preferably this string should be in ISO-8601 format
})
//the mapping step can be done in other ways most likely
.catch(this.handleError);
}