1

I have a structure I would like to flatten into one homogeneous array. The source array looks like this:

[
  {
    "countryCode": "CA",
    "countryName": "Canada",
    "states": [
      {
        "stateCode": "CAAB",
        "stateName": "Alberta",
        "countryCode": "CA",
        "stateAbbrev": "AB"
      },
      . . . 
      {
        "stateCode": "CAYT",
        "stateName": "Yukon Territory",
        "countryCode": "CA",
        "stateAbbrev": "YT"
      }
    ]
  },
  {
    "countryCode": "US",
    "countryName": "USA",
    "states": [
      {
        "stateCode": "USAK",
        "stateName": "Alaska",
        "countryCode": "US",
        "stateAbbrev": "AK"
      },
      . . .
      {
        "stateCode": "USWY",
        "stateName": "Wyoming",
        "countryCode": "US",
        "stateAbbrev": "WY"
      }
    ]
  }
]

What I would like to transform this into should look like this:

[
  {
    "value": "CA",
    "label": "Canada"
  },
  {
    "value": "CACB",
    "label": "Alberta"
  },
  . . .
  {
    "value": "CAYT",
    "label": "Yukon Territory"
  },
  {
    "value": "US",
    "label": "USA"
  },
  {
    "value": "USAK",
    "label": "Alaska"
  },
  . . .
  {
    "value": "USWY",
    "label": "Wyoming"
  }
]

So far I have:

let countries:Observable<ICountry[]> = 
   this.http.get<ICountry[]>(`${this.buildProUrl}/states`);

return countries.map(o => o.map(c => 
  <IStateDropDownItem>{value: c.countryCode, label: c.countryName}));

It seems like there ought to be a way to merge the states belonging to each country into the resulting observable array. I've read the concatMap, mergeMap and switchMap documentation but I can't quite figure out how to put it all together.

Colin
  • 331
  • 3
  • 19
  • This isn't anything to do with RxJS, if I understand correctly; you don't want to merge *streams*, you just want to change the object within the stream. The RxJS docs therefore won't help you at all, you need to write a vanilla TS/JS function that takes your input structure and returns your output structure, then `.map` that into your stream. – jonrsharpe Dec 21 '17 at 15:10
  • This has nothing to do with RxJS. You just want to transform an array of countries into an array of IStateDropDownItems. So all you need is the .map() operator that you're already using, and you must pass it a function that transforms an array of countries into an array of IStateDropDownItems. – JB Nizet Dec 21 '17 at 15:11
  • Perhaps your confusion is that you're using two different maps - `countries.map` is an `Observable.map`, whereas `o.map` is an `Array.map`. – jonrsharpe Dec 21 '17 at 15:15

1 Answers1

2

I think you only need to process your result array, which can be done using Arry.reduce() function:

const data = [
  {
    "countryCode": "CA",
    "countryName": "Canada",
    "states": [
      {
        "stateCode": "CAAB",
        "stateName": "Alberta",
        "countryCode": "CA",
        "stateAbbrev": "AB"
      },
      {
        "stateCode": "CAYT",
        "stateName": "Yukon Territory",
        "countryCode": "CA",
        "stateAbbrev": "YT"
      }
    ]
  },
  {
    "countryCode": "US",
    "countryName": "USA",
    "states": [
      {
        "stateCode": "USAK",
        "stateName": "Alaska",
        "countryCode": "US",
        "stateAbbrev": "AK"
      },
      {
        "stateCode": "USWY",
        "stateName": "Wyoming",
        "countryCode": "US",
        "stateAbbrev": "WY"
      }
    ]
  }
];

console.log(data.reduce((res, curr) => {
  res.push({value: curr.countryCode, label: curr.countryName});
  return res.concat(curr.states.reduce((res, curr) => {
    res.push({value: curr.stateCode, label: curr.stateName});
    return res;
  }, [])); 
}, []));

if you are using a new httpClient then your response is already an array, so in your case it should work:

let countries:Observable<ICountry[]> = 
  this.http.get<ICountry[]>(`${this.buildProUrl}/states`);

return countries.map(o => o.reduce((res, curr) => {
  res.push(<IStateDropDownItem>{value: curr.countryCode, label: curr.countryName});
  return res.concat(curr.states.reduce((res, curr) => {
    res.push(<IStateDropDownItem>{value: curr.stateCode, label: curr.stateName});
    return res;
  }, [])); 
}, []));
Andriy
  • 14,781
  • 4
  • 46
  • 50