AJ Hurst AJ Hurst - 2 months ago 29
Javascript Question

Using underscore to get all keys and list of unique values for each

Starting with an array of objects, I need get a list of all keys and all the unique values for each. The problem is that I don't know the keys beforehand. There are plenty of solutions if you know the key, but in this case each object can have any number of keys and each key has an array of values. The code below works, but it's quite complicated and there must be a simpler solution.

Made a working JSBIN here

Input:

[
{
key_1: [ attribute_value_1, attribute_value_2, ... ],
key_2: [ attribute_value_3, attribute_value_4, ... ],
},
...
]


Output:

[
{
label: key_1,
options: [ attribute_value_1, attribute_value_2, ... ]
},
{
label: key_2,
options: [ attribute_value_3, attribute_value_4, ... ]
},
...
]


Suggested Solution:

_.chain(input)
.map(function (attr) {
return _.keys(attr).map(function (key) {
return {
key: key,
value: attr[key]
};
});
})
.flatten()
.groupBy('key')
.map(function (grouped_values, key) {
// value = array of { key, value }
return {
label: key,
options: _.chain(grouped_values)
.pluck('value')
.flatten()
.uniq()
.value()
};
})
.value();

Answer

Using lodash - Apply _.mergeWith() to the input array, and use the customizer function to combine the arrays and get the unique values. Afterwards _.map() the result to the required format:

var input = [
  {
    key_1: [ "attribute_value_1", "attribute_value_2" ],
    key_2: [ "attribute_value_3", "attribute_value_4" ]
  },
  {
    key_1: [ "attribute_value_1", "attribute_value_5" ],
    key_2: [ "attribute_value_2", "attribute_value_3" ],
    key_5: [ "attribute_value_2", "attribute_value_3" ]
  }
];


var params = [{}].concat(input).concat(function (objValue, srcValue) { // create the params to apply to mergeWith
  if (_.isArray(objValue)) {
    return _.union(objValue, srcValue); // merge the arrays, and get the unique values
  }
});

var result = _.map(_.mergeWith.apply(_, params), function(value, key) { // merge all objects in the array, and map the results to required format
  return {
    label: key,
    options: value
    };
  });

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>

And you can clean up the ugly params array if you use ES6:

const input = [
  {
    key_1: [ "attribute_value_1", "attribute_value_2" ],
    key_2: [ "attribute_value_3", "attribute_value_4" ]
  },
  {
    key_1: [ "attribute_value_1", "attribute_value_5" ],
    key_2: [ "attribute_value_2", "attribute_value_3" ],
    key_5: [ "attribute_value_2", "attribute_value_3" ]
  }
];

const customizer = (objValue, srcValue) => {
  if (_.isArray(objValue)) {
    return _.union(objValue, srcValue); // merge the arrays, and get the unique values
  }
};

const result = _.map(_.mergeWith({}, ...input, customizer), (value, key) => ({ // merge all objects in the array, and map the results to required format
  label: key,
  options: value
}));

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>

Comments