hybrid9
hybrid9

Reputation: 2636

Grouping array of objects by multiple keys

I feel embarrassed for asking this question as I should know how to figure it out, but I'm spinning my wheels on grouping an array of objects by multiple keys.

Here's the data:

[
      {
        "car": "audi",
        "type": "A6",
        "style": "Avant",
        "year": "1996"
      },
      {

        "car": "audi",
        "type": "A4",
        "style": "2",
        "year": "2006"
      },
      {

        "car": "audi",
        "type": "A4",
        "style": "L W12",
        "year": "2006"
      },
      {

        "car": "audi",
        "type": "80",
        "style": "GLE",
        "year": "1975"
      },
      {

        "car": "audi",
        "type": "A6",
        "style": "Avant L",
        "year": "1996"
      },
      {
        "car": "audi",
        "type": "A6",
        "style": "3.2 Multitronic",
        "year": "2006"
      },
]

What I've been trying to generate with little success is the following:

 [{
   "audi": [{
     "1996": {
       "A6": ["Avant, Avant L"]
     }
   }, {
     "2006": }
       "A6": ["3.2 Multitronic"],
       "A4": ["L W12", "2"]
     }
   }
   ....
 }]

The schema is:

{
    "car1": [{
        "year1": {
            "style1": ["trim1", "trim2"],
            "style2": ["trim1", "trim2"]
        },
        "year1": {
            "style1": ["trim1", "trim2"],
            "style2": ["trim1", "trim2"]
        }
    }],
    "car2": [{
        "year1": {
            "style1": ["trim1", "trim2"],
            "style2": ["trim1", "trim2"]
        },
        "year2": {
            "style1": ["trim1", "trim2"],
            "style2": ["trim1", "trim2"]
        }
    }]
}

I've tried the following with lodash

  let result = _.chain(carData)
    .groupBy('car')
    .toPairs()
    .map(function(curr) {
        return _.zipObject(['car', 'year'], curr);
    })
    .value();

This gets me part of the way, but I end up with incomplete data when it comes to the styles and types for each year of the car.

Upvotes: 3

Views: 3219

Answers (3)

Amr Omar
Amr Omar

Reputation: 557

const groupBy = function groupBy(list, properties, propertyIndex)  {
      // current property index
      let i = propertyIndex === undefined ? 0 : propertyIndex;
    
      // group by
      let grouppedObj = list.reduce((acc, obj) => {
        let groupedValue = obj[properties[i]];
        if (!groupedValue) {
          return acc;
        }
        if (!acc[groupedValue]) {
          acc[groupedValue] = [];
        }
        acc[groupedValue].push({ ...obj, groupBy: properties.join(",") });
        return acc;
      }, {});
    
      // group by nested
      const keys = Object.keys(grouppedObj);
      if (i === properties.length - 1) {
        return grouppedObj;
      }
      keys.forEach((key) => {
        grouppedObj[key] = groupBy(grouppedObj[key], properties, i + 1);
      });
      return grouppedObj;
    };
    
    const data =[
  {
    "year": "2021",
    "cabin": "1",
    "months": ["1", "2"]
  },
  {
    "year": "2021",
    "cabin": "1",
    "months": ["4"]
  },
  {
    "year": "2021",
    "cabin": "2",
    "months": ["1", "2"]
  },
  {
    "year": "2022",
    "cabin": "1",
    "months": ["1", "2"]
  },
  {
    "year": "2022",
    "cabin": "1",
    "months": ["4"]
  },
  {
    "year": "2022",
    "cabin": "2",
    "months": ["1", "2"]
  }
];

 const results=groupBy(data, ["year", "cabin"]);
console.log(results);

Upvotes: 1

Rares Matei
Rares Matei

Reputation: 286

Here's a (slightly verbose) solution that generates exactly the JSON object shape you wanted and groups by unlimited keys:

var cars = [{
  "car": "audi",
  "type": "A6",
  "style": "Avant",
  "year": "1996"
}, {

  "car": "audi",
  "type": "A4",
  "style": "2",
  "year": "2006"
}, {

  "car": "audi",
  "type": "A4",
  "style": "L W12",
  "year": "2006"
}, {

  "car": "audi",
  "type": "80",
  "style": "GLE",
  "year": "1975"
}, {

  "car": "audi",
  "type": "A6",
  "style": "Avant L",
  "year": "1996"
}, {
  "car": "audi",
  "type": "A6",
  "style": "3.2 Multitronic",
  "year": "2006"
}, ];

function groupBy(list, prop) {
  return list.reduce((groupped, item) => {
    var key = item[prop];
    delete item[prop];
    if (groupped.hasOwnProperty(key)) {
      groupped[key].push(item);
    } else {
      groupped[key] = [item];
    }
    return groupped
  }, {});
}

function groupSubKeys(obj, properties, propIndex) {
  var grouppedObj = groupBy(obj, properties[propIndex]);
  Object.keys(grouppedObj).forEach((key) => {
    if (propIndex < properties.length - 2) {
      grouppedObj[key] = groupSubKeys(grouppedObj[key], properties, propIndex + 1);
    } else {
      grouppedObj[key] = grouppedObj[key].map(item => item[properties[propIndex + 1]])
    }
  });
  return grouppedObj;
}

function groupByProperties(list, properties) {
  return groupSubKeys(list, properties, 0);
}

console.log(groupByProperties(cars, ['car', 'year', 'type', 'style']));

Here's a running example: http://codepen.io/rarmatei/pen/evmBOo

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386654

You could use a hash object and a nested approach for the given properties.

var data = [{ car: "audi", type: "A6", style: "Avant", year: 1996 }, { car: "audi", type: "A4", style: 2, year: 2006 }, { car: "audi", type: "A4", style: "L W12", year: 2006 }, { car: "audi", type: 80, style: "GLE", year: 1975 }, { car: "audi", type: "A6", style: "Avant L", year: 1996 }, { car: "audi", type: "A6", style: "3.2 Multitronic", year: 2006 }],
    keys = ['car', 'year', 'type'],
    result = [];

data.forEach(function (a) {
    keys.reduce(function (r, k) {
        var o = {};
        if (!r[a[k]]) {
            r[a[k]] = { _: [] };
            o[a[k]] = r[a[k]]._;
            r._.push(o);
        }
        return r[a[k]];
    }, this)._.push(a.style);
}, { _: result });
   
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

Related Questions