Search code examples
jsonangularrxjsobservable

How to map an http .get observable response to handle object of arrays or array of objects


My code is below, it expects the endpoint to be in the form of an array of items like this [{},{},{}...]:

[
  {
    "id": 1,
    "name": "item1"
  },
  {
    "id": 1,
    "name": "item2"
  }
]

But some endpoints use a root node identifier/label like {[{},{},{}...]}

{
 "items": [
  {
    "id": 1, 
    "name": "item1"
  },
  {
    "id": 1, 
    "name": "item2"
  }
 ]
}

To handle the latter case, I have added the "startNode" parameter. For example, in the second JSON above, the code expects to pass in "items" as the startNode. I would like to refactor the response observable so that it automatically handles both formats without need to know the startNode:

getEndpoint(event: CustomEvent) {
  const mappedValues = mapValuesKeyBy(event.detail, 'name', 'value');
  let endpoint = trim(mappedValues._endpoint) || 'http://localhost:4100/states';
  const labelKey = trim(mappedValues._labelKey);
  const valueKey = trim(mappedValues._valueKey);
  const labelFormat = mappedValues._labelFormat;
  const hiddenColumns = trim(mappedValues._hiddenColumns);
  const startNode = trim(mappedValues._startNode);

  this.appService.get(endpoint, {}).subscribe(result => {
    
    if(typeof(result[0]) === 'undefined' && result[startNode]) {
      result = result[startNode];
    } else {
      endpoint = 'http://localhost:4100/states';
    }
    
    const titleMap: TitleMapItem[] = map(result, value => ({
        name: labelFormat ? this.jsf.parseVariables(labelFormat, value) : value[labelKey],
        value: value[valueKey]
      }));
      
    this.setTitleMap(event, titleMap);

    /** NOTE - remaps the original <mat-select> dynamic list to the Properties <mat-autocomplete> for setting the `defaultValue` */
    const tableConfig: AutoCompleteTableConfig = {
      labelKey,
      valueKey,
      hiddenColumns: hiddenColumns || []
    };
    this.setAutoCompleteList(event, result, tableConfig);
  });
}

An example of the first use case endpoint is here: https://jsonplaceholder.typicode.com/users

An example of the second use case endpoint is here: https://dummyjson.com/products


Solution

  • Probably better to properly type your endpoints, but here's how you can do it:

    this.appService.get(endpoint, {}).subscribe(result => {
        if (!Array.isArray(result)) {
            result = Object.values(result)[0];
        }
        
        // ...
    });
    

    This works by first checking if the result is an array , and by taking the first object value if it's not.

    Of course, this assumes that you only have array and single key objects. Like I said, better to properly type your endpoints.