3

How do I aggregate an array of objects on an object property in order to turn:

[ { key: 'black', value: [ '2', '3', '9' ] },
  { key: 'black', value: [ '1' ] },
  { key: 'gold', value: [ '2', '3' ] },
  { key: 'gold', value: [ '1' ] },
  { key: 'red', value: [ '9' ] },
  { key: 'white', value: [ '2', '3' ] },
  { key: 'white', value: [ '1' ] } ]

...into:

[ { key: 'black', value: [ '1', '2', '3', '9' ] },
  { key: 'gold', value: [ '1', '2', '3' ] },
  { key: 'red', value: [ '9' ] },
  { key: 'white', value: [ '1', '2', '3' ] } ]

...with javascript?

I feel like there should be a fairly straightforward way to do this with lodash or Array.reduce, but I cant for the life of me work out how to do it.

7 Answers 7

3

You could use a temporary object for referencing the groups and return an array with the result in a single loop.

var array = [{ key: 'black', value: ['2', '3', '9'] }, { key: 'black', value: ['1'] }, { key: 'gold', value: ['2', '3'] }, { key: 'gold', value: ['1'] }, { key: 'red', value: ['9'] }, { key: 'white', value: ['2', '3'] }, { key: 'white', value: ['1'] }],
    result = [];

array.forEach(function (a) {
    if (!this[a.key]) {
        this[a.key] = { key: a.key, value: [] };
        result.push(this[a.key]);
    }
    this[a.key].value = this[a.key].value.concat(a.value);
    this[a.key].value.sort();
}, {});

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

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

3 Comments

this should be referring to the window context.
@ryeballar, no, its thisArgs of Array#forEach. the content is an empty object {}.
Oh, sorry about that, didn't notice.
0

You could pretty easily write this yourself without any 3rd-party stuff:

var input = ...
var reduced = [];
var reducedMap = {};

input.forEach(function(obj) {
  if(!reduced.hasOwnProperty(obj.key) {
    reduced[obj.key] = [];
  }

  for(var i = 0; i < obj.value.length; i++) {
    if(reduced[obj.key].indexOf(obj.value[i]) < 0) {
      reduced[obj.key].push(obj.value[i]);
    }
  }
});

reduced = Object.keys(reducedMap).map(function(key){
  return {
    key: key,
    value: reducedMap[key].sort()
  };
});

Comments

0

You need to group first in an map and then produce the output you want

var output = {};
obj.forEach(function(val){
 output[val.key] = output[val.key] || [];
 output[val.key] = output[val.key].concat(val.value);
});

Now product the compressed output

var finalOutput = Object.keys(output).map(function(val){
      return { key: val, value: output[val] };
});

DEMO

var obj = [{
  key: 'black',
  value: ['2', '3', '9']
}, {
  key: 'black',
  value: ['1']
}, {
  key: 'gold',
  value: ['2', '3']
}, {
  key: 'gold',
  value: ['1']
}, {
  key: 'red',
  value: ['9']
}, {
  key: 'white',
  value: ['2', '3']
}, {
  key: 'white',
  value: ['1']
}];

var output = {};
obj.forEach(function(val) {
  var key = val.key;
  var value = val.value;
  output[key] = output[key] || [];
  output[key] = output[key].concat(val.value);
});

var finalOutput = Object.keys(output).map(function(val) {
  return {
    key: val,
    value: output[val]
  };
});

document.body.innerHTML += JSON.stringify(finalOutput,0,4);

Comments

0

You can use a combination of groupBy, map, and reduce to create the desired result:

  var result = 
      _(input)
          .groupBy('key')
          .map(function(d, key) {
              return {
                  key: key, 
                  value: _.reduce(d, function(a, dd) {
                      return a.concat(dd.value);
                  }, []).sort(),
              };
          })
          .value();

Fiddle here: https://jsfiddle.net/dw1zzfqt/1/

ES2015 Version

var result = 
    _(input)
        .groupBy('key')
        .map((d, key) => ({
            key, 
            value: _.reduce(d, (a, dd) => a.concat(dd.value), []).sort()
        }))
        .value();

Fiddle here: https://jsfiddle.net/dw1zzfqt/2/

Comments

0

var array = [{ key: 'black', value: ['2', '3', '9'] }, { key: 'black', value: ['1'] }, { key: 'gold', value: ['2', '3'] }, { key: 'gold', value: ['1'] }, { key: 'red', value: ['9'] }, { key: 'white', value: ['2', '3'] }, { key: 'white', value: ['1'] }],
    result = [];

array.forEach(function (a) {
    if (!this[a.key]) {
        this[a.key] = { key: a.key, value: [] };
        result.push(this[a.key]);
    }
    this[a.key].value = this[a.key].value.concat(a.value);
    this[a.key].value.sort();
}, {});

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

1 Comment

Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes.
0

Using lodash, as indicated by your question's tags:

const arr = [ { key: 'black', value: [ '2', '3', '9' ] },
      { key: 'black', value: [ '1' ] },
      { key: 'gold', value: [ '2', '3' ] },
      { key: 'gold', value: [ '1' ] },
      { key: 'red', value: [ '9' ] },
      { key: 'white', value: [ '2', '3' ] },
      { key: 'white', value: [ '1' ] } ];
      
    const newArr = _.chain(arr) // Lodash<number, { key, value }>
        .groupBy("key") // Lodash<key, { key, value }>
        .mapValues(items => {
            return _.chain(items) // Lodash<number, value>
                .reduce((values, item) => [...values, ...item.value], []) // Lodash<number, value>
                .sortBy(_.identity)
                .sortedUniq()
                .value(); // value
        }) // Lodash<key, value>
        .map((value, key) => ({ key, value }))  // Lodash<number, { key, value }>
        .value(); // { key, value }[]
    
    console.log({ arr, newArr });
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

We use _.chain to create a chain of transformations, and _.value to unwrap that chain into the computed result.

Comments

-1
const dedupe = a => [...new Set(a)].sort()

const arr = [
  { key: 'black', value: ['2', '3', '9', '1'] },
  { key: 'black', value: ['1'] },
  { key: 'gold', value: ['2', '3'] },
  { key: 'gold', value: ['1'] },
  { key: 'red', value: ['9'] },
  { key: 'white', value: ['3', '2'] },
  { key: 'white', value: ['1'] }
]

dedupe(arr.map(item => item.key)) // get unique, sorted array of 'key's
  .map(key =>
    arr
      .filter(item => item.key == key) // process each key in turn
      .reduce((a, c) => ({
        key,
        value: dedupe([...c.value, ...a.value]) // aggregate and sort the 'value's for each 'key'
      }))
  )


Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.