2

I'm trying to figure out how to convert an array of vehicle objects, that are only unique by trim/year, to an array nested object properties. Originally I was looping through all of the properties to organize the vehicles into a hierarchical structure of arrays i.e., make[model[trim[year[]]]], but I think it would be faster to look up the vehicles by object properties i.e., make.model.trim.year. I'm a lodash noob so I'm not sure how to go about this.

The data returned is structured like this:

[
  {
    id:1
    makeCode:"Make1"
    modelCode:"Modela"
    selected:true
    trimCode:"D"
    yearCode:"2014"
  },
  {
    id:2
    makeCode:"Make1"
    modelCode:"Modela"
    selected:true
    trimCode:"D"
    yearCode:"2015"
  },
  {
    id:3
    makeCode:"Make1"
    modelCode:"Modela"
    selected:true
    trimCode:"D"
    yearCode:"2016"
  },
  {
    id:4
    makeCode:"Make1"
    modelCode:"Modela"
    selected:true
    trimCode:"LX"
    yearCode:"2014"
  },
  {
    id:5
    makeCode:"Make1"
    modelCode:"Modela"
    selected:true
    trimCode:"LX"
    yearCode:"2015"
  },
  {
    id:6
    makeCode:"Make1"
    modelCode:"Modela"
    selected:true
    trimCode:"LX"
    yearCode:"2016"
  },
  {
    id:7
    makeCode:"Make2"
    modelCode:"Modelb"
    selected:true
    trimCode:"D"
    yearCode:"2014"
  },
  {
    id:8
    makeCode:"Make2"
    modelCode:"Modelb"
    selected:true
    trimCode:"D"
    yearCode:"2015"
  },
  {
    id:9
    makeCode:"Make2"
    modelCode:"Modelb"
    selected:true
    trimCode:"D"
    yearCode:"2016"
  }
]

This is working correctly to get the objects ordered by makeCode:

vm.makeGroups = _.groupBy(vm.selectedVehcileTypes, function(v) { return v.makeCode});

but I would like to do this on every level, so I have something like:

vm.makeGroups = _.groupBy(vm.selectedVehcileTypes, function(v) { return v.makeCode});
vm.modelGroups = _.groupBy(vm.makeGroups, function(v) { return v.modelCode});
vm.trimGroups = _.groupBy(vm.modelGroups, function(v) { return v.trimCode});

Basically I want to chain the grouping so that the end result looks like this:

{
  Make1: {
    modela: {
      D: {
           '2012': false,
           '2013': false 
           '2014': true // selected
         } 
       }
     }
   }
}

Any help is appreciated, thanks.

neridaj
  • 2,143
  • 9
  • 31
  • 62

1 Answers1

3

The solution below uses a combination of _.reduce() and _.setWith() as the primary functions to obtain the final result.

// array: Collection of objects
// keysPath: The path of the of the 
//           property to set check _.setWith docs
// keyValue: The value of each property

function getResult(array, keysPath, keyValue) {
  // This is used to get the values from the keysPath
  var getValue = _.curry(_.get, 2);
  return _.reduce(array, function(result, item) {
    // get path
    var path = _.map(keysPath, getValue(item));
    // set property values, note that using `Object` as a customizer
    // makes sure that yearCode is not treated as an index of an array
    // but an index of an object
    return _.setWith(result, path, item[keyValue], Object);
  }, {});
}

var result = getResult(
  data,
  ['makeCode', 'modelCode', 'trimCode', 'yearCode'],
  'selected'
);

var data = [
  {
    id:1,
    makeCode:"Make1",
    modelCode:"Modela",
    selected:true,
    trimCode:"D",
    yearCode:"2014"
  },
  {
    id:2,
    makeCode:"Make1",
    modelCode:"Modela",
    selected:true,
    trimCode:"D",
    yearCode:"2015"
  },
  {
    id:3,
    makeCode:"Make1",
    modelCode:"Modela",
    selected:true,
    trimCode:"D",
    yearCode:"2016"
  },
  {
    id:4,
    makeCode:"Make1",
    modelCode:"Modela",
    selected:true,
    trimCode:"LX",
    yearCode:"2014"
  },
  {
    id:5,
    makeCode:"Make1",
    modelCode:"Modela",
    selected:true,
    trimCode:"LX",
    yearCode:"2015"
  },
  {
    id:6,
    makeCode:"Make1",
    modelCode:"Modela",
    selected:true,
    trimCode:"LX",
    yearCode:"2016"
  },
  {
    id:7,
    makeCode:"Make2",
    modelCode:"Modelb",
    selected:true,
    trimCode:"D",
    yearCode:"2014"
  },
  {
    id:8,
    makeCode:"Make2",
    modelCode:"Modelb",
    selected:true,
    trimCode:"D",
    yearCode:"2015"
  },
  {
    id:9,
    makeCode:"Make2",
    modelCode:"Modelb",
    selected:true,
    trimCode:"D",
    yearCode:"2016"
  }
];

function getResult(array, keysPath, keyValue) {
  var getValue = _.curry(_.get, 2);
  return _.reduce(array, function(result, item) {
    var path = _.map(keysPath, getValue(item));
    return _.setWith(result, path, item[keyValue], Object);
  }, {});
}

var result = getResult(
  data,
  ['makeCode', 'modelCode', 'trimCode', 'yearCode'],
  'selected'
);

document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.js"></script>
ryeballar
  • 29,658
  • 10
  • 65
  • 74
  • Wow, thanks for the response. Still trying to get my head around this. I tried running the code and I'm getting '_.setWith is not a function'. I'm using lodash version 14.4.1, any ideas? – neridaj Aug 09 '16 at 20:13
  • Are you certain that `_` variable you're using is lodash and not undescore? and the version is `4.14.1`? Please re-check. – ryeballar Aug 10 '16 at 02:13
  • Yeah, I'm not using underscore and verified my lodash version - "lodash": "4.14.1. If I change it to _.set it seems to work? – neridaj Aug 16 '16 at 18:41
  • It shouldn't be working, try adding the code that you mentioned works using `_.set()` in the question, I'll try to check why it's behaving that way. – ryeballar Aug 16 '16 at 19:00
  • Been away from this for a bit but it turns out that another library I'm using, pdfMake, includes it's own custom build of lodash version 3.10.1. Appears to be fixed by changing the order they are imported. Thanks again, man! – neridaj Sep 20 '16 at 00:11