typescript load json data from url into arrays

Viewed 2959

I have json data coming in from a URL. The data looks like this:

[
  {"id":1,"symbol":"SP-500","date":"1927-12-30T07:00:00.000+00:00","open":17.66,"high":17.66,"low":17.66,"close":17.66,"volume":0},
  {"id":2,"symbol":"SP-500","date":"1928-01-03T07:00:00.000+00:00","open":17.76,"high":17.76,"low":17.76,"close":17.76,"volume":0}
]

The code to retrieve it is in quote.service.ts and looks like this:

  getQuotesList(): Observable<any> {
    return this.http.get(`${this.baseUrl}`);
  }

Here is the whole class:

        import { Injectable } from '@angular/core';
        import { HttpClient } from '@angular/common/http';
        import { Observable, Subject } from 'rxjs';
        import { of } from 'rxjs';
        import 'rxjs/add/operator/map'
        import 'rxjs/operator/filter';
        import { Quote } from "./quote";
        
        @Injectable({
          providedIn: 'root'
        })
        export class QuoteService {
        
          private baseUrl = 'http://localhost:8080/springboot-crud-rest/api/v1/quotes';
        
          constructor(private http: HttpClient) { }
        
          getQuote(id: number): Observable<any> {
            return this.http.get(`${this.baseUrl}/${id}`);
          }
        
          createQuote(quote: Object): Observable<Object> {
            return this.http.post(`${this.baseUrl}`, quote);
          }
        
          updateQuote(id: number, value: any): Observable<Object> {
            return this.http.put(`${this.baseUrl}/${id}`, value);
          }
        
          deleteQuote(id: number): Observable<any> {
            return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
          }
        
          //getQuotesList(): Observable<any> {
          //  return this.http.get(`${this.baseUrl}`);
          //}
    
    getQuotesList(): Observable<[string[], string[]]> {
      // replace of(data) to this.http.get(this.baseUrl)
      return of(data).pipe(
        map((quotes) => {
          return quotes.reduce((acc, curr) => {
            acc[0].push(curr.date.substr(0, 10));
            acc[1].push(curr.open);
            return acc;
          }, [[], []]);
        }),
      )
    }
}

The data is then sent on to quote-list.component.ts which successfully loads it into the quotes array like this:

export class QuoteListComponent implements OnInit {
  quotes: Observable<Quote[]>;

It then passes quotes on to quote-list.component.html and builds a table using the following code:

<tr *ngFor="let quote of quotes | async">
  <td>{{quote.symbol}}</td>
  <td>{{quote.date}}</td>
  <td>{{quote.open}}</td>
  <td>{{quote.high}}</td>
  <td>{{quote.low}}</td>
  <td>{{quote.close}}</td>
  <td>{{quote.volume}}</td>
  <td><button (click)="deleteQuote(quote.id)" class="btn btn-danger">Delete</button>
      <button (click)="quoteDetails(quote.id)" class="btn btn-info" style="margin-left: 10px">Details</button>
  </td>
</tr>

The table looks nice and all the data is there.

Now I want to load it into a separate array for each value, like this:

[1927-12-30,1928-01-03]
[17.66,17.76]

etc.

And then I want to make those arrays available to a javascript charting program which will plot the data. That plotting code requires a separate array for each value.

I thought that if I modified quote.service.ts to look something like

  getQuotesList(): Observable<any> {
    let quotes = this.http.get(`${this.baseUrl}`);
    let opens = quotes.map(theOpen => open);
    console.log("opens.length=" + opens.length);
    opens.forEach(function(item,index,array) {
        console.log(item,index);
        })
    return quotes;
  }

But get the error:

ERROR in quote.service.ts:38:23 - error TS2339: Property 'map' does not exist on type 'Observable<Object>'.

38    let opens = quotes.map(theOpen => open);

So I changed getQuotesList(): Observable<any> to getQuotesList(): Observable<Quote[]>

But got the following error:

ERROR in quote.service.ts:38:23 - error TS2339: Property 'map' does not exist on type 'Observable<Object>'.

38    let opens = quotes.map(theOpen => open);
                         ~~~
quote.service.ts:43:4 - error TS2322: Type 'Observable<Object>' is not assignable to type 'Observable<Quote[]>'.
  The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead?
    Type 'Object' is missing the following properties from type 'Quote[]': length, pop, push, concat, and 26 more.

So I changed let opens = quotes.map(theOpen => open); to let opens = quotes.pipe(map(theOpen => open));

But got the error

ERROR in quote.service.ts:39:40 - error TS2339: Property 'length' does not exist on type 'Observable<(url?: string, target?: string, features?: string, replace?: boolean) => Window>'.

39    console.log("opens.length=" + opens.length);
                                          ~~~~~~
quote.service.ts:40:18 - error TS2345: Argument of type '(item: any, index: any, array: any) => void' is not assignable to parameter of type '(value: (url?: string, target?: string, features?: string, replace?: boolean) => Window) => void'.

40    opens.forEach(function(item,index,array) {

Then I tried:

getQuotesList() {
  let quotes = this.http.get<Quote[]>(`${this.baseUrl}`);
  let opens = quotes.pipe(map(theOpen => open));
  alert(opens[0]);
  return quotes;
}

But the output was "undefined"

Then I tried

private quoteObserver = {
  next(quote) {
    console.log("quote.open=" +quote.open);
  }
}

getQuotesList() {
  let quotes = this.http.get<Quote[]>(`${this.baseUrl}`);
  of(quotes).subscribe(this.quoteObserver);
  return quotes;
}

but the output was quote.open=undefined

Then I tried

  getQuotesList(): Observable<any> {
    let quotes = this.http.get(`${this.baseUrl}`);
    quotes.subscribe(arr => {
      Object.values(arr[0].forEach(el => {
        let col = arr.map(ele => ele[el]);
        console.log(el,':', col);
      }
    )}
    return quotes;
  }

But the error was

ERROR in quote.service.ts:67:6 - error TS1005: ',' expected.

67     )}
        ~
quote.service.ts:68:5 - error TS1005: ',' expected.

68     return quotes;
       ~~~~~~


    ERROR in quote.service.ts:64:23 - error TS2339: Property 'map' does not exist on type 'Object'.

    64         let col = arr.map(ele => ele[el]);

Do you think I've made enough attempts yet? :-( I'm new to Angular/Typescript so hoping someone can guide me.

EDIT: I managed to figure how to port the code over to a linux server. Now you can access the back end to see the actual data coming in by entering this in the browser: 167.172.141.34:8080/springboot-crud-rest/api/v1/quotes

7 Answers

There are two pieces to this question. One is how to convert the JSON data to the array format that you want, and the other issue is how to work with Observables which wrap the actual data you're trying to access.

For the first piece, here is sample code which should convert the data format:

const quotes = [
  {"id":1,"symbol":"SP-500","date":"1927-12-30T07:00:00.000+00:00","open":17.66,"high":17.66,"low":17.66,"close":17.66,"volume":0},
  {"id":2,"symbol":"SP-500","date":"1928-01-03T07:00:00.000+00:00","open":17.76,"high":17.76,"low":17.76,"close":17.76,"volume":0}
] 

const chartData = Object.assign({}, 
  ...Object.keys(quotes[0]).map(key => ({
    [key]: quotes.map(quote => quote[key])
  })))

console.log(chartData)

And to get the data out of the observable you just subscribe to it. I've included the conversion code from above as a separate function but you could inline it as well:

getQuotesList() {
  const quotes = this.http.get(`${this.baseUrl}`)
  quotes.subscribe(quotes => this.toChart(quotes))
  return quotes
}

toChart (quotes) {
  const chartData = Object.assign({}, 
      ...Object.keys(quotes[0]).map(key => ({
          [key]: quotes.map(quote => quote[key])
      })))
  console.log(chartData)
}

You can use mapping

var arr=[
  {"id":1,"symbol":"SP-500","date":"1927-12-30T07:00:00.000+00:00","open":17.66,"high":17.66,"low":17.66,"close":17.66,"volume":0},
  {"id":2,"symbol":"SP-500","date":"1928-01-03T07:00:00.000+00:00","open":17.76,"high":17.76,"low":17.76,"close":17.76,"volume":0}
]



Object.keys(arr[0]).forEach(el=>{
  let arrray= (el=="date") ? arr.map(ele=> ele[el].substring(0,10)) : arr.map(ele=>ele[el]);
  console.log(el,':', arrray);
  // use your logic here
})

use subscrribe

return this.http.get(`${this.baseUrl}`).subscribe(arr=>{
   Object.keys(arr[0]).forEach(el=>{
      let arrray= (el=="date") ? arr.map(ele=> ele[el].substring(0,10)) : 
      arr.map(ele=>ele[el]);
      console.log(el,':', arrray);
      // use your logic here
   })
});

The subscribe approach is correct, but you are not handing the resulting array in the right way. Insided the subscribe callback you get an actual array, so you can use your original .map approach that was not working on the observer object in your attempt #1

getQuotesList(): Observable<any> {
  let quotes = this.http.get<Quote[]>(`${this.baseUrl}`);
  quotes.subscribe((arr: Quote[]) => {
    const opens = arr.map(quote => quote.open)
    console.log(opens); // [17.66,17.76]

    const dates = arr.map(quote => quote.date.split("T")[0])
    console.log(dates); // [1927-12-30,1928-01-03]
  )}
  return quotes;
}

This code smells a lot... I think that the QuoteService method named "getQuotesList"should only have one purpose: return an Observable<Quote[]> as you did in the beginning.

What I think you need to do in your component is to store the quote observable, and create a new observable with a switchMap operator (with reduce logic inside).

you should check this stackblitz for possible solution : https://stackblitz.com/edit/rxjs-uurnuv?file=index.ts

Cheers!

You have error in getQuotesList().

import { of, Observable } from 'rxjs'; 
import { map } from 'rxjs/operators';

const data = [
  {"id":1,"symbol":"SP-500","date":"1927-12-30T07:00:00.000+00:00","open":17.66,"high":17.66,"low":17.66,"close":17.66,"volume":0},
  {"id":2,"symbol":"SP-500","date":"1928-01-03T07:00:00.000+00:00","open":17.76,"high":17.76,"low":17.76,"close":17.76,"volume":0}
];

function getQuotesList(): Observable<[string[], string[]]> {
  // replace of(data) to this.http.get(this.baseUrl)
  return of(data).pipe(
    map((quotes) => {
      return quotes.reduce((acc, curr) => {
        acc[0].push(curr.date.substr(0, 10));
        acc[1].push(curr.open);
        return acc;
      }, [[], []]);
    }),
  )
}

getQuotesList().subscribe(x => console.log(x));

https://stackblitz.com/edit/rxjs-j8ptal?file=index.ts

UPDATE:

It is full class of your service:

import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable, Subject, of } from "rxjs";
import { map } from 'rxjs/operators';
import "rxjs/add/operator/map";
import "rxjs/operator/filter";
import { Quote } from "./quote";

@Injectable({
  providedIn: "root"
})
export class QuoteService {
  private baseUrl = "http://localhost:8080/springboot-crud-rest/api/v1/quotes";

  constructor(private http: HttpClient) {}

  getQuote(id: number): Observable<any> {
    return this.http.get(`${this.baseUrl}/${id}`);
  }

  createQuote(quote: Object): Observable<Object> {
    return this.http.post(`${this.baseUrl}`, quote);
  }

  updateQuote(id: number, value: any): Observable<Object> {
    return this.http.put(`${this.baseUrl}/${id}`, value);
  }

  deleteQuote(id: number): Observable<any> {
    return this.http.delete(`${this.baseUrl}/${id}`, { responseType: "text" });
  }

  //getQuotesList(): Observable<any> {
  //  return this.http.get(`${this.baseUrl}`);
  //}

  getQuotesList(): Observable<[string[], string[]]> {
    // replace of(data) to this.http.get(this.baseUrl)
    return this.http.get<Quote[]>(this.baseUrl).pipe(
      map(quotes => {
        return quotes.reduce(
          (acc, curr) => {
            acc[0].push(curr.date.substr(0, 10));
            acc[1].push(curr.open);
            return acc;
          },
          [[], []]
        );
      })
    );
  }
}

I try to understand what you want.

  1. You want to parse json data to data(typed Object all something)
  2. You want to aggregate data toArrayValues.

Right?

I make some code for you.

interface Quote {
  id: number;
  symbol: string;
  date: string;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

const jsonList = [{
    "id": 1,
    "symbol": "SP-500",
    "date": "1927-12-30T07:00:00.000+00:00",
    "open": 17.66,
    "high": 17.66,
    "low": 17.66,
    "close": 17.66,
    "volume": 0
  },
  {
    "id": 2,
    "symbol": "SP-600",
    "date": "1928-01-03T07:00:00.000+00:00",
    "open": 17.76,
    "high": 17.76,
    "low": 17.76,
    "close": 17.76,
    "volume": 0
  }
]


function jsonParse < T = any > (json: any): T | null {
  try {
    return JSON.parse(JSON.stringify(json));
  } catch (error) {
    console.error('ERROR')
    return null;
  }
}

function aggregate < T = any > (dataList: T[]) {
  const aggregation = dataList.reduce < T[keyof T][][] > ((acc, data) => {
    Object.values(data).forEach((value: T[keyof T], index: number) => {
      if (Array.isArray(acc[index])) {
        acc[index].push(value);
        return acc;
      }
      acc[index] = [value]
      return acc;
    })
    return acc;
  }, []);

  return aggregation;
}

const quotes = jsonParse < Quote[] > (jsonList);
if (quotes) {
  const aggregationQuotes = aggregate < Quote > (quotes);
  console.log('quotes', quotes)
  console.log('aggregationQuotes', aggregationQuotes)
}

I finally got it to compile without error by first running this:

npm install --save rxjs-compat

And then adding this import:

import { map } from 'rxjs/operators';

And then adding the following code to get one array of "open" values:

  getQuotesList(): Observable<any> {
    let quotes = this.http.get(`${this.baseUrl}`);
    let opens = quotes.pipe(map(theOpen => open));
    console.log(opens);
    return quotes;
  }

Unfortunately, nothing is output by console.log(opens), but that is another problem.

Related