Property 'json' does not exist on type 'Object'

164.5k views Asked by At

I'm trying fetch data via REST with angular 2 HttpClient. I'm following the angular tutorial here https://angular.io/tutorial/toh-pt6 and under the Heroes and HTTP section you'll see this snippet of code used to fetch hero data via http.

getHeroes(): Promise<Hero[]> {
  return this.http.get(this.heroesUrl)
         .toPromise()
         .then(response => response.json().data as Hero[])
         .catch(this.handleError);
}

And below is a similar version I wrote in my application

fetch(startIndex: number, limit: number): Promise<Order[]> {
    let url = this.baseUrl + "&startIndex=" + startIndex + "&limit=" + limit;

    return this.http.get(url)
                .toPromise()
                .then(response => response.json().results as Order[])
                .catch(this.handleError);
}

I'm using InteliJ Idea and there's a red line on the call response.json() and even when I try to build using ng build I get the error.

Property 'json' does not exist on type 'Object'.

You may notice that instead of json().data I have json().results. That's because according to the tutorial the server responded with an object that has a data field but my own server responds with an object that has a results field. If you scroll down the tutorial a bit you'll see this point.

Note the shape of the data that the server returns. This particular in-memory web API example returns an object with a data property. Your API might return something else. Adjust the code to match your web API.

In an attempt to fix this, I tried something like this

(response: Response) => response.json().results as Order[]

When I did that the .json() method was been resolved but another error popped up that

Property results does not exist on type Promise

I tried fixing that by defining an interface

interface OrderResponse {
    orders: Order[];
}

And modified the get call to

 .get<OrderResponse>(url)...

But that also didn't work. Another error popped up

Type 'OrderResponse' is not assignable to type 'Response'.

One thing note is that, in the tutorial they used the Angular HttpModule but in my application I'm using the new Angular HttpClientModule so maybe that's where the error is coming.

I'm new to Angular 2 and this is the first app I'm building with it. If the above code is no longer valid with the new HttpClientModule I'd appreciate any help on how to achieve the same with the new HttpClientModule.

I found similar questions Property 'json' does not exist on type '{}' and Property does not exist on type 'object' but none of the answers there helped me.

Update

As the comments suggested there is no .json() method in the new HttpClientModule. I'd still appreciate help on how to achieve the same effect with the new module. From the guide they did something like this

http.get<ItemsResponse>('/api/items').subscribe(data => {
  // data is now an instance of type ItemsResponse, so you can do this:
  this.results = data.results;
});

Which I understand perfectly but my problem is, that code is not within a component but a service so calling subscribe and assigning the result to a instance field won't make much sense.

I need my service to return an array of Orders wrapped in a Promise. The my components can just make calls like

this.orderService.fetch(0, 10).then(orders => this.orders = orders)

I also thought of declaring a local variable in my service fetch method so that I can do

.subscribe(data => {
    this.orders = data.results;
}
// and out of the get call I return my local variable orders like
return Promise.resolve(orders)

But that doesn't make much sense to me as the call to .get() is asynchronous and the method may return even before all the data is fetch and the orders array may be empty.

Update

As requested here is the code for handleError

private handleError(error: any): Promise<any> {
    console.log('An error occured ', error);
    return Promise.reject(error.message || error);
}
5

There are 5 answers

7
Jota.Toledo On BEST ANSWER

UPDATE: for rxjs > v5.5

As mentioned in some of the comments and other answers, by default the HttpClient deserializes the content of a response into an object. Some of its methods allow passing a generic type argument in order to duck-type the result. Thats why there is no json() method anymore.

import {throwError} from 'rxjs';
import {catchError, map} from 'rxjs/operators';

export interface Order {
  // Properties
}

interface ResponseOrders {
  results: Order[];
}

@Injectable()
export class FooService {
 ctor(private http: HttpClient){}

 fetch(startIndex: number, limit: number): Observable<Order[]> {
    let params = new HttpParams();
    params = params.set('startIndex',startIndex.toString()).set('limit',limit.toString());
    // base URL should not have ? in it at the en
    return this.http.get<ResponseOrders >(this.baseUrl,{
       params
    }).pipe(
       map(res => res.results || []),
       catchError(error => _throwError(error.message || error))
    );
} 

Notice that you could easily transform the returned Observable to a Promise by simply invoking toPromise().

ORIGINAL ANSWER:

In your case, you can

Assumming that your backend returns something like:

{results: [{},{}]}

in JSON format, where every {} is a serialized object, you would need the following:

// Somewhere in your src folder

export interface Order {
  // Properties
}

import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

import { Order } from 'somewhere_in_src';    

@Injectable()
export class FooService {
 ctor(private http: HttpClient){}

 fetch(startIndex: number, limit: number): Observable<Order[]> {
    let params = new HttpParams();
    params = params.set('startIndex',startIndex.toString()).set('limit',limit.toString());
    // base URL should not have ? in it at the en
    return this.http.get(this.baseUrl,{
       params
    })
    .map(res => res.results as Order[] || []); 
   // in case that the property results in the res POJO doesnt exist (res.results returns null) then return empty array ([])
  }
} 

I removed the catch section, as this could be archived through a HTTP interceptor. Check the docs. As example:

https://gist.github.com/jotatoledo/765c7f6d8a755613cafca97e83313b90

And to consume you just need to call it like:

// In some component for example
this.fooService.fetch(...).subscribe(data => ...); // data is Order[]
1
Voicu On

For future visitors: In the new HttpClient (Angular 4.3+), the response object is JSON by default, so you don't need to do response.json().data anymore. Just use response directly.

Example (modified from the official documentation):

import { HttpClient } from '@angular/common/http';

@Component(...)
export class YourComponent implements OnInit {

  // Inject HttpClient into your component or service.
  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    this.http.get('https://api.github.com/users')
        .subscribe(response => console.log(response));
  }
}

Don't forget to import it and include the module under imports in your project's app.module.ts:

...
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    // Include it under 'imports' in your application module after BrowserModule.
    HttpClientModule,
    ...
  ],
  ...
1
Lakkoju Mahendra On

If your response from API is an object containing data as { status: 200, data: [{},{},...{}], message: 'Data fetched successfully'} you can do as below.

first converting response to string and then parse the response and get data assigned to variable which is array of objects.

this._employeeService.getEmployees()
 .pipe(map(response => JSON.stringify(response)))
    .subscribe(result => {
      this.employeeList = JSON.parse(result).data;
     // console.log(this.employeeList);
    },(error)=>{
      console.log(error);
    })
0
Griffin On

fix for me on dotnet API angular MVC application

check your api url, trailing /api/ was removed when i updated the url. without it the root url is returning html, that is the <

2
kagiso william On

The other way to tackle it is to use this code snippet:

JSON.parse(JSON.stringify(response)).data

This feels so wrong but it works