1

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);
}
Gelo
  • 67
  • 1
  • 10
  • potential duplicate of [http://stackoverflow.com/q/22875636/1160236](http://stackoverflow.com/q/22875636/1160236) – HirenParekh Mar 02 '17 at 06:19
  • Possible duplicate of [How do I cast a JSON object to a typescript class](http://stackoverflow.com/questions/22875636/how-do-i-cast-a-json-object-to-a-typescript-class) – HirenParekh Mar 02 '17 at 06:21
  • 1
    @HirenParekh I saw that question, but I wasn't found what to do, when properties doesn't match. – Gelo Mar 02 '17 at 06:26
  • Using a class is a very bad practice here since `instanceof` will fail at runtime is anyone tries to use it (hint use `interface` not `class`). You need to manually write a function that verifies the shape of the deserialized object in order to validate it. – Aluan Haddad Mar 02 '17 at 06:29
  • Also, what is `handleError`? If it is a method on the service that uses `this`, passing it as shown will fail. If it does not use `this` why is it a member of the class? – Aluan Haddad Mar 02 '17 at 06:33
  • you can try this [class-transformer](https://github.com/pleerock/class-transformer) library – HirenParekh Mar 02 '17 at 06:53

2 Answers2

1

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);
}
Hoang Hiep
  • 2,242
  • 5
  • 13
  • 17
  • This does not do what is asked for. It does not validate the deserialized object. – Aluan Haddad Mar 02 '17 at 06:30
  • 3
    @AluanHaddad actually, the OP explicitly mentioned that the "properties don't match"; so the question is not about validation. – Henry Mar 02 '17 at 07:47
  • That is not how I interpreted it, but at any rate the function should modify the OP by not returning something declared as a class since that is misleading given the implementation. – Aluan Haddad Mar 02 '17 at 07:49
1

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.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84