Reputation: 67
For example, I have a class:
export class SomeClass {
id: number;
name: string;
}
I receive JSON from server than looks like this
[{"Id":1,"Name":"typicalname"},{"Id":2,"Name":"somename"},{"Id":3,"Name":"blablabla"},{"Id":4,"Name":"lol"},{"Id":5,"Name":"lil"},{"Id":6,"Name":"lal"}]
How do I cast a JSON object to a typescript class when properties doesn't match? That's how I do it wright now, and it's not working.
getSomeClass() {
return this.http.get(this.someClassUrl)
.map(response => <SomeClass[]>response.json())
.catch(this.handleError);
}
Upvotes: 1
Views: 2033
Reputation: 31823
When you have a type T
and a value x
and you write <T>x
you are not performing a cast in a runtime sense. You are performing a type assertion. What this means is that you are telling TypeScript that the type of x
is T
.
In this particular case, if response.json()
returns a value typed as any
, which is not unreasonable for a deserialization operation, then <T>response.json()
will be accepted by the TypeScript compiler for any T
. This is because the type any
is compatible with (technically assignable to) everything.
However in this case you want to verify the shape of the response and the compiler cannot do this for you. You need to write a validation algorithm that is appropriate.
What is appropriate will depend on the domain of your application, and may be non-trivial, but here is an example. Unfortunately, since your question implies Angular 2 and RxJS, even a simple applicable answer contains a fair amount of incidental complexity.
import {Http} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
function injected(_) {} // emit decorator metadata with flag (not pertinent)
@injected export class SomeService {
constructor(readonly http: Http) {}
getSomeValue(): Observable<Expected> {
return this.http.get(this.someResourceUrl)
.catch(handleError)
.mergeMap(response => {
const deserialized = response.json();
if (isExpected(deserialized)) {
// note the type of derserialized is Expected in this block
return Observable.of(deserialized);
}
return Observable.throw('response data did not have the expected shape');
});
}
}
export interface Expected {
id: number;
name: string;
}
function isExpected(deserialized : any): deserialized is Expected {
return typeof deserialized.id === 'number' && typeof deserialized.name === 'string';
}
function handleError(error) { // this is not really necessary, but was in the question
console.error(error); // log
return Observable.throw(error); // rethrow.
}
The most significant thing here is the isExpected
function.
It takes a value of any type, validates it based on our criteria, and states that if it returns true, then the given value was indeed of the expected type, Expected
.
What does it mean to be of the expected type?
Well our isExpected
function determines that, and provides this information to the TypeScript language by way of its return type which says that if the function returns true, then the value passed to it is of type Expected
.
This is known as a User Defined Type Guard function and you can read more about it at https://www.typescriptlang.org/docs/handbook/advanced-types.html.
Upvotes: 1
Reputation: 2412
try this:
getSomeClass() {
return this.http.get(this.someClassUrl)
.map(response => {
let json = response.json();
return json.map(m => {
return {
id: json.Id,
name: json.Name
}
}
})
.catch(this.handleError);
}
Upvotes: 1