How to get nested data in the response of a Json WebService?


#1

Hi,
I’ve a web service that returns this json:

{
    "Response": "Success",
    "Message": "Coin list succesfully returned! This api is moving to https://min-api.cryptocompare.com/data/all/coinlist, please change the path.",
    "BaseImageUrl": "https://www.cryptocompare.com",
    "BaseLinkUrl": "https://www.cryptocompare.com",
    "DefaultWatchlist": {
        "CoinIs": "1182,7605,5038,24854,3807,3808,202330,5324,5031,20131",
        "Sponsored": ""
    },
    "SponosoredNews": [
        {
            "id": 907274,
            "guid": "https://goo.gl/x2wT5k",
            "published_on": 1530020965,
            "imageurl": "https://www.cryptocompare.com/media/34077391/onlineio200x200.gif?anchor=center&mode=crop&width=300&height=300&rnd=131744945650000000",
            "title": "Online.io - Earn Tokens to Browse The Internet",
            "url": "https://goo.gl/x2wT5k",
            "source": "CryptoCompare",
            "body": "Online.io is implementing a decentralized ecosystem which combines all necessary features for a faster, safer, and more private Internet browsing. Moreover, through the Online.io platform developed by using the most advanced Blockchain technology, both internet end users and website operators will benefit alike.\r\n\r\nThe Online (OIO) token is a unique digital asset that will unleash the Internet from ads, malware and tracking software, leading to a more enjoyable and secure browsing experience, with sizeable benefits for both web operators and end users.",
            "tags": "ICO",
            "categories": "ICO",
            "lang": "EN",
            "source_info": {}
        }
    ],
    "Data": {
        "42": {
            "Id": "4321",
            "Url": "/coins/42/overview",
            "ImageUrl": "/media/12318415/42.png",
            "Name": "42",
            "Symbol": "42",
            "CoinName": "42 Coin",
            "FullName": "42 Coin (42)",
            "Algorithm": "Scrypt",
            "ProofType": "PoW/PoS",
            "FullyPremined": "0",
            "TotalCoinSupply": "42",
            "PreMinedValue": "N/A",
            "TotalCoinsFreeFloat": "N/A",
            "SortOrder": "34",
            "Sponsored": false
        },
        "300": {
            "Id": "749869",
            "Url": "/coins/300/overview",
            "ImageUrl": "/media/27010595/300.png",
            "Name": "300",
            "Symbol": "300",
            "CoinName": "300 token",
            "FullName": "300 token (300)",
            "Algorithm": "N/A",
            "ProofType": "N/A",
            "FullyPremined": "0",
            "TotalCoinSupply": "300",
            "PreMinedValue": "N/A",
            "TotalCoinsFreeFloat": "N/A",
            "SortOrder": "2212",
            "Sponsored": false
        },
......

I have to print the Name and TotalCoinSupply of each Data item?

I’m using this function into a Providers to get the data using HttpClient:

  getCoinList() {
    return new Promise(resolve => {
      this.http.get(this.apiUrlCrypto+'/coinlist',{headers: this.cryptoHeaders}).subscribe(data => {
        resolve(data);
      }, err => {
        console.log(err);
      });
    });

and this function in a Ionic page to call the provider function:

  getCoins(){
    console.log('getCoinsList');
    this.users = [];
    this.coins = [];
    this.wsManager.getCoinList()
      .then(data => {
        this.coins = data;
      });
  }

I don’t know how to navigate the content of the attribute this.coins and how to
check its content.

cld


#2

Don’t instantiate a Promise here, simply return the Observable that HttpClient gives you and work with that. Do define interfaces for all domain data that is coming in over the wire, and do declare explicit return types for every function you write. If you take the time to do that, your development environment will guide you through how to unpack the data.

Start from the inside and work your way out:

export interface Coin {
  Id: string;
  Url: string;
  ImageUrl: string;
  Name: string;
  // and so on
}

export interface CoinMap {
  [name: string]: Coin;
}

export interface CoinsResponse {
  Response: "Success" | "Error";
  Message: string;
  // ...
  Data: CoinMap;
}

export class CoinProvider {
  fetchCoins(): Observable<CoinsResponse> {
    return this.http.get(api);
  }
}

Assuming you are going to want to loop across these in an ngFor, I would take this a step further and have the provider transform the CoinMap into a Coin[] and return that instead. That would also give you a convenient place to convert all the property names to more conventional camelCase before it ever gets to pages.


#3

Thank you @rapropos I will try to follow your suggestion it seems the right way to do things.

But why do you discourage the use of a Promise? beacause it is useless or because it should be done in the controller of the page?

cld


#4

It’s not so much that I discourage use of Promise here as it is needless explicit construction of one. If your client code really wants a Promise, I would use the toPromise operator to make it.


#5

Excuse me, why don’t you use the Map function in your example ?
When I fetch data from a web service and I want to put them into an object isn’t Map the function I should use?
I’ve found many ways to do this, but I’ve not still found a definitive guide to map Rest data into Typescript objects.

cld


#6

You certainly could, and I would do so to (for example) fix the Odd Capitalization Of Many Of The Properties In The Incoming Object.

Especially if you are using interfaces for business objects in your TypeScript code (which I recommend), all that is necessary is putting the proper generic type into HttpClient methods:

interface Foo {
  bar: string;
}

class FooService {
   allFoos(): Observable<Foo[]> {
    return this._http.get<Foo[]>(url);
   }
}

map is only needed when the incoming JSON is shaped differently from what you are wanting:

{
  "Data": [ {Bar: "baz"}, {Bar: "frotz"} ]
}

Now allFoos has to do a bit more work:

interface IncomingFoo {
  Bar: string;
}

interface IncomingResponse {
  Data: Bar[];
}

allFoos(): Observable<Foo[]> {
  return this._http.get<IncomingResponse>(url).pipe(
    map(rsp => rsp.Data),
    map(ifoos => ifoos.Data.map(ifoo => {bar: ifoo.Bar})));
}