davidchoo12
davidchoo12

Reputation: 1331

How to group a javascript object array by multiple of its properties?

I want to convert showtimesData to showtimesByLocationByDate

Any idea how to do it without using any third party library and just using pure javascript? Otherwise, what third party library can I use for this?

    var showtimesData = [
        {"location":"location1", "date":"31-12-2016", "time":"1:00"},
        {"location":"location1", "date":"31-12-2016", "time":"2:00"},
        {"location":"location1", "date":"01-01-2017", "time":"3:00"},
        {"location":"location1", "date":"01-01-2017", "time":"4:00"},
        {"location":"location2", "date":"31-12-2016", "time":"1:00"},
        {"location":"location2", "date":"31-12-2016", "time":"2:00"},
        {"location":"location2", "date":"01-01-2017", "time":"3:00"},
        {"location":"location2", "date":"01-01-2017", "time":"4:00"},
    ];
    var showtimesByLocationByDate = [
        {
            "location":"location1",
            "dates":[
                {
                    "date":"31-12-2016",
                    "times":["1:00","2:00"]
                },
                {
                    "date":"01-01-2017",
                    "times":["3:00","4:00"]
                }
            ]
        },
        {
            "location":"location2",
            "dates":[
                {
                    "date":"31-12-2016",
                    "times":["1:00","2:00"]
                },
                {
                    "date":"01-01-2017",
                    "times":["3:00","4:00"]
                }
            ]
        },
    ];

Upvotes: 6

Views: 524

Answers (2)

Nina Scholz
Nina Scholz

Reputation: 386868

This proposal features just Array.prototype.reduce() with one temporary object for referencing the array items.

var showtimesData = [{ "location": "location1", "date": "31-12-2016", "time": "1:00" }, { "location": "location1", "date": "31-12-2016", "time": "2:00" }, { "location": "location1", "date": "01-01-2017", "time": "3:00" }, { "location": "location1", "date": "01-01-2017", "time": "4:00" }, { "location": "location2", "date": "31-12-2016", "time": "1:00" }, { "location": "location2", "date": "31-12-2016", "time": "2:00" }, { "location": "location2", "date": "01-01-2017", "time": "3:00" }, { "location": "location2", "date": "01-01-2017", "time": "4:00" }, ],
    showtimesByLocationByDate = showtimesData.reduce(function (r, a) {
        var o;
        if (!(a.location in r.obj)) {
            o = { location: a.location, dates: [] };
            r.obj[a.location] = { dates: o.dates };
            r.array.push(o);
        }
        if (!(a.date in r.obj[a.location])) {
            o = { date: a.date, times: [] };
            r.obj[a.location].dates.push(o);
            r.obj[a.location][a.date] = o.times;
        }
        r.obj[a.location][a.date].push(a.time);
        return r;
    }, { array: [], obj: {} }).array;

document.write('<pre>' + JSON.stringify(showtimesByLocationByDate, 0, 4) + '</pre>');

Bonus: Generic version with a given data structure

var showtimesData = [{ "location": "location1", "date": "31-12-2016", "time": "1:00" }, { "location": "location1", "date": "31-12-2016", "time": "2:00" }, { "location": "location1", "date": "01-01-2017", "time": "3:00" }, { "location": "location1", "date": "01-01-2017", "time": "4:00" }, { "location": "location2", "date": "31-12-2016", "time": "1:00" }, { "location": "location2", "date": "31-12-2016", "time": "2:00" }, { "location": "location2", "date": "01-01-2017", "time": "3:00" }, { "location": "location2", "date": "01-01-2017", "time": "4:00" }, ],
    structure = [
        { key: 'location', data: 'dates' },
        { key: 'date', data: 'times' },
        { key: 'time' }
    ],
    showtimesByLocationByDate = showtimesData.reduce(function (r, a) {
        var properties = structure.slice(),
            lastKey = properties.pop().key;

        properties.reduce(function (rr, b) {
            var o = {},
                p = {},
                key = b.key,
                value = a[key],
                array = b.data;

            if (!(value in rr.obj)) {
                o[key] = value;
                o[array] = [];
                p[array] = o[array];
                rr.obj[value] = p;
                rr.array.push(o);
            }
            return { array: rr.obj[value][array], obj: rr.obj[value] };
        }, r).array.push(a[lastKey]);

        return r;
    }, { array: [], obj: {} }).array;

document.write('<pre>' + JSON.stringify(showtimesByLocationByDate, 0, 4) + '</pre>');

Upvotes: 4

MinusFour
MinusFour

Reputation: 14423

I'd propose this transformation:

var showtimesData = [
        {"location":"location1", "date":"31-12-2016", "time":"1:00"},
        {"location":"location1", "date":"31-12-2016", "time":"2:00"},
        {"location":"location1", "date":"01-01-2017", "time":"3:00"},
        {"location":"location1", "date":"01-01-2017", "time":"4:00"},
        {"location":"location2", "date":"31-12-2016", "time":"1:00"},
        {"location":"location2", "date":"31-12-2016", "time":"2:00"},
        {"location":"location2", "date":"01-01-2017", "time":"3:00"},
        {"location":"location2", "date":"01-01-2017", "time":"4:00"},
    ];
  
var transformed = showtimesData.reduce(function(obj, show){
  //var { location, date, time } = show; //if destructuring is available
  var location = show.location,
      date = show.date,
      time = show.time,
      objLocation = obj[location] = obj[location] || { dates : { } },
      dates = objLocation.dates,
      date = dates[date] = dates[date] || [ ];

      date.push(time);
      return obj;
}, {});
results.innerHTML = JSON.stringify(transformed, null, '\t');
<pre id="results"></pre>

But if you really want to transform it to that, I'd propose grabing this transformation and map it to your proposed structure.

var showtimesData = [
        {"location":"location1", "date":"31-12-2016", "time":"1:00"},
        {"location":"location1", "date":"31-12-2016", "time":"2:00"},
        {"location":"location1", "date":"01-01-2017", "time":"3:00"},
        {"location":"location1", "date":"01-01-2017", "time":"4:00"},
        {"location":"location2", "date":"31-12-2016", "time":"1:00"},
        {"location":"location2", "date":"31-12-2016", "time":"2:00"},
        {"location":"location2", "date":"01-01-2017", "time":"3:00"},
        {"location":"location2", "date":"01-01-2017", "time":"4:00"},
    ];
  
var transformed = showtimesData.reduce(function(obj, show){
  //var { location, date, time } = show; //if destructuring is available
  var location = show.location,
      date = show.date,
      time = show.time,
      objLocation = obj[location] = obj[location] || { dates : { } },
      dates = objLocation.dates,
      date = dates[date] = dates[date] || [ ];

      date.push(time);
      return obj;
}, {});

var secondTransformed = Object.keys(transformed).map(function(key){
  var dates = transformed[key].dates,
      transformedDates = Object.keys(dates).map(function(key){
          return { date : key, times : dates[key] }
      });
  return { location : key, dates : transformedDates }
});
results.innerHTML = JSON.stringify(secondTransformed, null, '\t');
<pre id="results"></pre>

Although there are better ways to do this (performance wise).

Upvotes: 4

Related Questions