Search code examples
javascriptarraysecmascript-6lodash

Merge and Dedupe Array of Complex Objects with Arrays


I have a pretty complex problem that I can't seem to figure out. I have two array of objects that I would like to merge scores for. It should merge/append certain properties based on the scores. For example between the two arrays there are 4 total gameId's with 3 of them being unique. When merging it should combine the _scores section if it's the same gameId so in this case it would be both EarthNormal merging. But the problem is sometimes the score in _scores can have duplicate scores so the BAR and BASH almost look the exact same but are different it can be appended but FOO score is the exact same on both so I don't want it merged into the scores (if that makes sense).

const arr1 = [{
  "gameId": "AirNormal",
  "_scores":
    [{
      "score": 144701,
      "playerName": "FOO",
      "fullCombo": true,
      "timestamp": 1599968866
    }]
}, {
  "gameId": "EarthNormal",
  "_scores":
    [{
      "score": 177352,
      "playerName": "BAR",
      "fullCombo": true,
      "timestamp": 1599969253
    }, {
      "score": 164665,
      "playerName": "FOO",
      "fullCombo": false,
      "timestamp": 1599970971
    }]
}];

const arr2 = [{
  "gameId": "EarthNormal",
  "_scores":
    [{
      "score": 177352,
      "playerName": "BASH",
      "fullCombo": false,
      "timestamp": 1512969017
    }, {
      "score": 164665,
      "playerName": "FOO",
      "fullCombo": false,
      "timestamp": 1599970971
    }]
}, {
  "gameId": "FireNormal",
  "_scores":
    [{
      "_score": 124701,
      "_playerName": "FOO",
      "_fullCombo": true,
      "_timestamp": 1591954866
    }]
}];

I would want the final merged array to look like:

mergedArray = [{
  "gameId": "AirNormal",
  "_scores":
    [{
      "score": 144701,
      "playerName": "FOO",
      "fullCombo": true,
      "timestamp": 1599968866
    }]
}, {
  "gameId": "EarthNormal",
  "_scores":
    [{
      "score": 177352,
      "playerName": "BAR",
      "fullCombo": true,
      "timestamp": 1599969253
    }, {
      "score": 177352,
      "playerName": "BASH",
      "fullCombo": false,
      "timestamp": 1512969017
    }, {
      "score": 164665,
      "playerName": "FOO",
      "fullCombo": false,
      "timestamp": 1599970971
    }]
}, {
  "gameId": "FireNormal",
  "_scores":
    [{
      "score": 124701,
      "playerName": "FOO",
      "fullCombo": true,
      "timestamp": 1591954866
    }]
}]

I have tried doing this and using lodash:

let merged = [...arr1, ...arr2];

merged = _.uniqBy[merged, 'gameId']
let scoresMerge = _.uniqBy[merged, '_scores']

console.log(scoresMerge);

but it didn't work as I expected. Am I approaching this incorrectly?


Solution

  • This is fairly straight forward using vanilla javascript.

    • merge the arrays using destructuring
    • reduce() the merged arrays into an object indexed by gameId
    • check all properties of each _score object against the accumulated _scores array using .some() and push if no match is found.
    • return the values of the reduced object using Object.values()

    const arr1 = [{  "gameId": "AirNormal",  "_scores":    [{      "score": 144701,      "playerName": "FOO",      "fullCombo": true,      "timestamp": 1599968866    }]}, {  "gameId": "EarthNormal",  "_scores":    [{      "score": 177352,      "playerName": "BAR",      "fullCombo": true,      "timestamp": 1599969253    }, {      "score": 164665,      "playerName": "FOO",      "fullCombo": false,      "timestamp": 1599970971    }]}];
    
    const arr2 = [{"gameId": "EarthNormal","_scores":[{"score": 177352,"playerName": "BASH","fullCombo": false,"timestamp": 1512969017}, {"score": 164665,"playerName": "FOO","fullCombo": false,"timestamp": 1599970971}]}, {"gameId": "FireNormal","_scores":[{"_score": 124701,"_playerName": "FOO","_fullCombo": true,"_timestamp": 1591954866}]}];
    
    
    const merged = Object.values([...arr1, ...arr2].reduce((a, {gameId, _scores}) => {
      // retrieve gameId object otherwise initialize it.
      a[gameId] = {...a[gameId] ?? {gameId, _scores: []}};
      // iterate over all _score objects
      _scores.forEach(s => {
        // if accumulator _scores array doesn't have an object matching all properties, push _score 
        if (!a[gameId]['_scores'].some(o => {
            return !Object.entries(s).some(([k, v]) => o[k] !== v)})
          ) {
          a[gameId]['_scores'].push({...s});
        }
      });  
      return a;
    }, {}));
    
    console.log(merged);