1

At the moment I am working with an api that returns an array of objects that looks like

[{match_id: "255232",
country_id: "41",
country_name: "England",
league_id: "152",
league_name: "National League",
match_date: "2020-01-01",
match_status: "",
match_time: "16:00"},
{match_id: "255232",
    country_id: "41",
    country_name: "Italy",
    league_id: "152",
    league_name: "Serie a",
    match_date: "2020-01-01",
    match_status: "",
    match_time: "16:00"},
{match_id: "255232",
        country_id: "41",
        country_name: "Italy",
        league_id: "153",
        league_name: "Serie b",
        match_date: "2020-01-01",
        match_status: "",
        match_time: "16:00"},...
    ...

]

I would like to transform it in a way that ends up looking like:

const transformed = [
{
    country_name: "England",
    entries: [
        {league_name: "National League",
        events: [{},{}]}
    ]

},
{country_name: "Italy",
entries: [
    {
        league_name: "Serie a",
        events: [{},{}]
    },
    {
        league_name: "Serie b",
        events: [{},{}]
    }...
]]

I have tried to use .reduce but did not end up with expected output and I end up with just a reverted structure. Basically what I need is to catalogue by country_name first and league_name second.

Obviously the data is dynamic and the names of countries/leagues change often.

2
  • 1
    I would try using map to iterate over the list and return a new list formatted as you want. Commented Dec 31, 2019 at 19:00
  • 1
    A couple of the answers have suggested using a keyed Object for your resulting data. I think the recommendation is good, and would advise that you consider how important the resulting "shape" (per @tex) of the data is to the eventual output. Commented Dec 31, 2019 at 19:44

4 Answers 4

2

I've provided two solutions - one that returns an object keyed by country and league names (this would be my preference / recommendation) and one that extends the first solution to return the shape you requested.

This first snippet transforms your input into an object keyed by country and league names:

const data = [{match_id: "255232",country_id: "41",country_name: "England",league_id: "152",league_name: "National League",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255233",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255234",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"}]

const transformed = data.reduce(
  (acc, { country_name, league_name, ...match }) => {
    acc[country_name] = acc[country_name] || {}
    acc[country_name][league_name] = acc[country_name][league_name] || []
    acc[country_name][league_name].push(match)
    return acc
  },
  {}
)

console.log(transformed)

This second snippet extends the first one, returning the shape you requested, originally:

const data = [{match_id: "255232",country_id: "41",country_name: "England",league_id: "152",league_name: "National League",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255233",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255234",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"}]

const tmp = data.reduce(
  (acc, { country_name, league_name, ...match }) => {
    acc[country_name] = acc[country_name] || {}
    acc[country_name][league_name] = acc[country_name][league_name] || []
    acc[country_name][league_name].push(match)
    return acc
  },
  {}
)

const transformed = Object.entries(tmp).map(
  ([country_name, leagues]) => ({
      country_name,
      entries: Object.entries(leagues).map(
        ([league_name, events]) => ({ league_name, events })
      )
  })
)

console.log(transformed)

There are a few reasons I prefer the keyed object to the array:

  1. There's less nesting in the keyed object, and it will be smaller when stringified.
  2. It's easier to get all entries for a country and/or a country + league from the object (e.g. transformed.England["National League"]).
  3. It's less work to generate the object.
  4. One entry per country means the 'correct' data structure is Map (correct in most but not necessarily all cases). The object is a better approximation of a Map than the array.

I use (and teach) a similar technique with success in redux. Rather than just storing an array of objects returned by an API, I also store an object based on the array that is indexed by the objects' ids. It's much easier to work with this object than it is with the raw array in many cases:

const arr = [{ id: 'a', foo: 'bar' }, { id: 'b', foo: 'baz' }]
const obj = { a: { foo: 'bar' }, b: { foo: 'baz' } }

Here's a quick snippet showing how to go from the object to your desired React output, if it turns out the object is a useful intermediate data structure:

const { Fragment } = React

const data = [{match_id: "255232",country_id: "41",country_name: "England",league_id: "152",league_name: "National League",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255233",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"},{match_id: "255234",country_id: "41",country_name: "Italy",league_id: "152",league_name: "Serie a",match_date: "2020-01-01",match_status: "",match_time: "16:00"}]

const transformed = data.reduce(
  (acc, { country_name, league_name, ...match }) => {
    acc[country_name] = acc[country_name] || {}
    acc[country_name][league_name] = acc[country_name][league_name] || []
    acc[country_name][league_name].push(match)
    return acc
  },
  {}
)

const League = ({ league, matches }) => (
  <Fragment>
    <h2>{league}</h2>
    {matches.map(({ match_id }) => (<p>{match_id}</p>))}
  </Fragment>
)

ReactDOM.render(
  Object.entries(transformed).map(([country, leagues]) => (
    <Fragment>
      <h1>{country}</h1>
      {Object.entries(leagues).map(([league, matches]) => (
        <League league={league} matches={matches} />
      ))}
    </Fragment>
  )),
  document.body
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Sign up to request clarification or add additional context in comments.

5 Comments

this looks great but why do you prefer the object vs the array structure?
@greatTeacherOnizuka Thanks! I added some notes about why I prefer the object to the array in the answer. Let me know if you have any other questions, and I'll do my best to answer them.
how do you go for printing all the entries of the object assuming you don't know the value of the key? I end up with something like Object.keys(transformed).map( key => { console.log(`${key} : ${transformed[key]}`) Object.keys(transformed[key]).forEach(k=>{ {(Object.values(transformed[key]).map(el => el.map(console.log(el.match_id)} }); } ) which doesnt look right. PS I am using React so you can just write in JSX if you prefer
I am trying to have a structure that looks like <H1>England</H1><H2>PREMIER LEAGUE</H2> <P>{MATCH_ID}</P>...
@greatTeacherOnizuka added a React snippet to show how you might generate your desired output from the intermediate object data structure.
1

This seems like a use case for a for loop, that creates or pushes each "result" into the appropriate Country index, then uses similar logic to organize the matches for each country by League. The code below is pretty verbose-- if you use a utility library like Lodash, you can achieve some of this much more succinctly via something like groupBy (although, that would produce an object instead of an array).

const results = [/* ...your original data... */];
const resultsByCountry = [];

// The first loop just indexes the matches by Country:
for (let r = 0; r < results.length; r++) {
  let result = results[r];
  let existingCountryIndex = resultsByCountry.findIndex((country) => country.country_name == result.country_name);

  // Temporarily, we're just stashing the matches flat, here. We'll take another pass to group them into leagues, once we know they're all sorted into the right countries:
  if (existingCountryIndex > -1) {
    resultsByCountry[existingCountryIndex].entries.push(result);
  } else {
    resultsByCountry.push({
      country_name: result.country_name,
      entries: [result]
    });
  }
}


// The second loop organizes the matches into leagues, by a very similar means:
for (let c = 0; c < resultsByCountry.length; c++) {
  const country = resultsByCountry[c]
  let matches = country.entries;
  let matchesByLeague = [];

  for (let m = 0; m < matches.length; m++) {
    let match = matches[m];
    let existingLeagueIndex = matchesByLeague.findIndex((league) => league.league_name == match.league_name);

    if (existingLeagueIndex > -1) {
      matchesByLeague[existingLeagueIndex].events.push(match);
    } else {
      matchesByLeague.push({
        league_name: match.league_name,
        events: [match]
      });
    }
  }

  // Overwrite the old `entries` key with the grouped array:
  country.entries = matchesByLeague;
}

console.log(resultsByCountry);

Again, this is pretty crude. There is probably a way to implement it with map and reduce, but because the data structure is unusual, and the question specifically asked about transforming from an array to an array (not an object), I've provided this as a simple + explicit answer.

Comments

1

You could store all information for building nested group/value pairs and take an array for all keys for the final objects and reduce the array by reducing the group array.

var data = [{ match_id: "255232", country_id: "41", country_name: "England", league_id: "152", league_name: "National League", match_date: "2020-01-01", match_status: "", match_time: "16:00" }, { match_id: "255232", country_id: "41", country_name: "Italy", league_id: "152", league_name: "Serie a", match_date: "2020-01-01", match_status: "", match_time: "16:00" }, { match_id: "255232", country_id: "41", country_name: "Italy", league_id: "153", league_name: "Serie b", match_date: "2020-01-01", match_status: "", match_time: "16:00" }],
    groups = [['country_name', 'entries'], ['league_name', 'events']],
    values = ['match_date', 'match_status', 'match_time'],
    result = data.reduce((r, o) => {
        groups
            .reduce((group, [key, values]) => {
                var temp = group.find(q => q[key] === o[key]);
                if (!temp) group.push(temp = { [key]: o[key], [values]: [] });
                return temp[values];
            }, r)
            .push(Object.assign(...values.map(k => ({ [k]: o[k] }))));
        return r;
    }, []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Comments

0
const map1 = {};
const list = matches.map(match => {
  if (!map1[match.country_name]) {
    map1[match.country_name] = {};
  }
  map1[match.country_name]["entries"] =
    map1[match.country_name]["entries"] &&
    map1[match.country_name]["entries"].length > 0
      ? map1[match.country_name]["entries"].concat(match)
      : [match];
});

const result = [];
Object.keys(map1).forEach(country => {
  const m = {};
  m["country_name"] = country;
  m["entries"] = [];
  const entries = map1[country]["entries"];

  entries.forEach(entry => {
    m["entries"].push({
      league_name: entry.league_name,
      events: entry
    });
  });

  result.push(m);
});
// result has the required info.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.