25

I need to combine two json arrays, delivered by two rest services. The entries with the same "id" belong together.

json1 = [{id:1,name:'aaa'},
     {id:5,name:'ccc'},
     {id:3,name:'bbb'}
   ];

 json2 = [{id:3,parameter1:'x', parameter2:'y', parameter3:'z'},
     {id:1,parameter1:'u', parameter2:'v', parameter3:'w'},
     {id:5,parameter1:'q', parameter2:'w', parameter3:'e'}
    ];

I need a combined/copied/cloned json array in javascript in the following way (my model in angular2):

json3 = [{id:3,name:'bbb',parameter1:'x', parameter2:'y',   parameter3:'z'},
     {id:1,name:'aaa', parameter1:'u', parameter2:'v', parameter3:'w'},
     {id:5,name:'ccc', parameter1:'q', parameter2:'w', parameter3:'e'}
    ];

Is there a way to combine them? The parameter names are not defined exactly and it needs to work with variable parameter vectors.

I tried it with mixed for each loops. Seems to me very ugly.

KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
user2083142
  • 431
  • 1
  • 6
  • 16
  • Possible duplicate of [How to merge two arrays in Javascript and de-duplicate items](http://stackoverflow.com/questions/1584370/how-to-merge-two-arrays-in-javascript-and-de-duplicate-items) – Luke Mar 09 '16 at 22:22
  • 2
    @Doctus, that question does not deal with nested properties. – trincot Mar 09 '16 at 22:24
  • @Doctus, i think my question is different. I do not just want to combine and eliminate double entries, i want to merge the key-value pairs of both json arrays, based on a unique key in the json objects. – user2083142 Mar 09 '16 at 22:27
  • > I tried it with mixed for each loops. Seems to me very ugly. - try `forEach` with arrow functions - it would look a bit better. – weaknespase Mar 09 '16 at 22:42
  • Possible duplicate of [Merge array of javascript objects by property key](http://stackoverflow.com/questions/32481446/merge-array-of-javascript-objects-by-property-key) – Heretic Monkey Mar 09 '16 at 22:44
  • This question is ***not*** a duplicate of [How to merge two arrays in JavaScript and de-duplicate items](https://stackoverflow.com/q/35903850). The two questions are essentially different. – Henke Mar 08 '21 at 09:54
  • If you are interested in this question, chances are that you will also want to look at [Merge two array of objects based on a key](https://stackoverflow.com/q/46849286). – Henke Apr 15 '21 at 15:20

12 Answers12

58

Two one-liners:

with lodash:

res = _(json1).concat(json2).groupBy('id').map(_.spread(_.assign)).value();

in ES2015:

res = json2.map(x => Object.assign(x, json1.find(y => y.id == x.id)));
georg
  • 211,518
  • 52
  • 313
  • 390
  • I wrote a set of functions for this last night in 8 lines and about 2 hrs. I used only map and filter. How the hell did I forget that there are other Array methods?! Upvote for Object.assign though didn't think of that either. Used a spread operator. Yours is sorter and easier to read. – xerotolerant Sep 13 '17 at 09:33
  • Is there an equivalent in underscore? – Marjorie Roswell Sep 22 '17 at 03:13
  • 4
    Is there a way to merge the new items from json2 also, not only that exists on both? – Joel Oct 08 '18 at 20:27
  • @Joel I just found this sample of outerJoin using Ramda: https://github.com/ramda/ramda/wiki/Cookbook#sql-style-joins `const joinOuter = R.curry((f1, f2, t1, t2) => { let o1 = R.indexBy(f1, t1); let o2 = R.indexBy(f2, t2); return R.values(R.mergeWith(R.merge, o1, o2)); }); // usage: joinOuter(R.prop('id'), R.prop('buyer'), people, transactions) // result: // [{ id: 1, name: 'me', buyer: 1, seller: 10 }, // { buyer: 2, seller: 5 }, // { id: 3, name: 'you' }] ` – audunsol Feb 20 '20 at 10:52
  • I have found that the **Lodash** solution is a _full outer join_, meaning each item from both `json1` and `json2` are merged into the result - irrespective of whether the item is also in the other array. The **Object.assign** solution is a _left outer join_, meaning _all_ items from `json1` go into the result, but items in `json2` are joined into the result _only_ if they also exist in `json1`. For a solution giving a _true inner join_, see [this answer](https://stackoverflow.com/a/67299551). – Henke Apr 30 '21 at 14:36
  • It deserves to be noted that for both the Lodash and the Object.assign solutions, the left array is _updated_ to hold the result. If you want to avoid this, see [Stack snippets of the two one-liners](https://stackoverflow.com/a/66659834). – Henke May 02 '21 at 10:47
11

ES2015 georg's answer works great;

    json1 = [
    {id:1, test: 0},
    {id:2, test: 0},
    {id:3, test: 0},
    {id:4, test: 0},
    {id:5, test: 0}
];

json2 = [
    {id:1, test: 1},
    {id:3, test: 1},
    {id:5, test: 1}
];

json1.map(x => Object.assign(x, json2.find(y => y.id == x.id)));

result:

{id:1, test: 1},
{id:2, test: 0},
{id:3, test: 1},
{id:4, test: 0},
{id:5, test: 1}
Killian Charlez
  • 119
  • 1
  • 4
6

If you wanted to write it so that you could take in any number of arrays, not just 2, you could utilize arguments, and do something like this:

var json1 = [{id:1,name:'aaa'},{id:5,name:'ccc'},{id:3,name:'bbb'}];

var json2 = [{id:3,parameter1:'x', parameter2:'y', parameter3:'z'},
             {id:1,parameter1:'u', parameter2:'v', parameter3:'w'},
             {id:5,parameter1:'q', parameter2:'w', parameter3:'e'}];

function joinObjects() {
  var idMap = {};
  // Iterate over arguments
  for(var i = 0; i < arguments.length; i++) {
    // Iterate over individual argument arrays (aka json1, json2)
    for(var j = 0; j < arguments[i].length; j++) {
      var currentID = arguments[i][j]['id'];
      if(!idMap[currentID]) {
        idMap[currentID] = {};
      }
      // Iterate over properties of objects in arrays (aka id, name, etc.)
      for(key in arguments[i][j]) {
        idMap[currentID][key] = arguments[i][j][key];
      }
    }
  }
  
  // push properties of idMap into an array
  var newArray = [];
  for(property in idMap) {
    newArray.push(idMap[property]);
  }
  return newArray;
}

var json3 = joinObjects(json1, json2);

console.log(JSON.stringify(json3));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here is a working codepen.

Henke
  • 4,445
  • 3
  • 31
  • 44
Jacob Petersen
  • 1,463
  • 1
  • 9
  • 17
5

let json1 = [
  { id: 1, name: 'aaa' },
  { id: 5, name: 'ccc' },
  { id: 3, name: 'bbb' }
];

let json2 = [
  { id: 3, parameter1: 'x', parameter2: 'y', parameter3: 'z' },
  { id: 1, parameter1: 'u', parameter2: 'v', parameter3: 'w' },
  { id: 5, parameter1: 'q', parameter2: 'w', parameter3: 'e' }
];

let result = json1.map(obj => {
  let data = json2.find(item => item.id === obj.id);
  return {...obj, ...data}
});

console.log(result);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
PrisonPants
  • 65
  • 1
  • 4
  • A nicer looking answer than many here. You might want to [make this a stack snippet](https://meta.stackoverflow.com/questions/35899). – Scott Sauyet Aug 22 '19 at 18:33
2

JavaScript:

let json1 = [
  { id: 1, name: 'aaa' },
  { id: 5, name: 'ccc' },
  { id: 3, name: 'bbb' }
];

let json2 = [
  { id: 3, parameter1: 'x', parameter2: 'y', parameter3: 'z' },
  { id: 1, parameter1: 'u', parameter2: 'v', parameter3: 'w' },
  { id: 5, parameter1: 'q', parameter2: 'w', parameter3: 'e' }
];

let json3 = [];

json1.forEach((j1) => {
  json2.forEach((j2) => {
    if (j1.id === j2.id) {
      json3.push({ ...j1, ...j2 });
    }
  });
});

console.log(JSON.stringify(json3));
.as-console-wrapper { top: 0; max-height: 100% !important; }
Henke
  • 4,445
  • 3
  • 31
  • 44
CodeLabs
  • 21
  • 2
  • This is a fine answer, demonstrating a _true inner join_ of the two arrays. In fact, I have not seen _any_ other answer to this question that does exactly that. Nor among the answers to the questions [Merge two array of objects based on a key](https://stackoverflow.com/q/46849286) or [How can I perform an inner join with two object arrays in JavaScript?](https://stackoverflow.com/q/42429023). (The last question _verbally_ asks for an _inner join_, but by the description and the output example it is actually a question asking for a _full outer join_.) – Henke Apr 28 '21 at 12:44
1

Use nested loops to find the corresponding elements and merge them.

for (var i = 0; i < json1.length; i++) {
    var id = json1[i].id;
    for (var j = 0; j < json2.length; j++) {
        if (json2[j].id == id) {
            for (var key in json2[j]) {
                json1[i][key] = json2[j][key];
            }
            break;
        }
    }
}

At the end, json1 will contain the combined elements.

The above code assumes that every element of json2 matches something in json1. If there can be extra elements in json2, you'll need an additional loop afterward to copy those over to json1.

kujiy
  • 5,833
  • 1
  • 29
  • 35
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • on large data set it would be more effective to convert smaller array into sparse array (by using id as index) and cut nested loop. – weaknespase Mar 09 '16 at 22:39
  • Yeah, that's how I usually do it, I decided to write the quick-and-dirty version this time. – Barmar Mar 09 '16 at 22:40
1

Here is a way where you first build an index keyed by id (sparse array) to detect and combine objects with matching id values, which then finally are concatenated back into a normal array:

json3 = json1.concat(json2).reduce(function(index, obj) {
    if (!index[obj.id]) {
        index[obj.id] = obj;
    } else {
        for (prop in obj) {
            index[obj.id][prop] = obj[prop];
        }
    }
    return index;
}, []).filter(function(res, obj) {
    return obj;
});

json1 = [
    {id:1,name:'aaa'},
    {id:5,name:'ccc'},
    {id:3,name:'bbb'}
];

json2 = [
    {id:3,parameter1:'x', parameter2:'y', parameter3:'z'},
    {id:1,parameter1:'u', parameter2:'v', parameter3:'w'},
    {id:5,parameter1:'q', parameter2:'w', parameter3:'e'}
];

json3 = json1.concat(json2).reduce(function(index, obj) {
    if (!index[obj.id]) {
        index[obj.id] = obj;
    } else {
        for (prop in obj) {
            index[obj.id][prop] = obj[prop];
        }
    }
    return index;
}, []).filter(function(res, obj) {
    return obj;
});

document.write('<pre>', JSON.stringify(json3, null, 4), '</pre>');

If your browser supports Object.assign:

json3 = json1.concat(json2).reduce(function(index, obj) {
    index[obj.id] = Object.assign({}, obj, index[obj.id]);
    return index;
}, []).filter(function(res, obj) {
    return obj;
});
trincot
  • 317,000
  • 35
  • 244
  • 286
1

Use forEach and filter we can resolve the requirement.

vehicleArray1 = [{id:1, name: "a"},{id:2, name: "b"},{id:3, name:"c"}];
vehicleArray2 = [{id:1, type: "two wheeler"},{id:2, type: "four wheeler"},{id:3, type:"six wheeler"}];
var outArr = [];
vehicleArray1.forEach(function(value) {
    var existing = vehicleArray2.filter(function(v, i) {
        return (v.id == value.id);
    });
    if (existing.length) {
        value.type = existing[0].type;
        outArr.push(value)
    } else {
        value.type = '';
        outArr.push(value);
    }
});
console.log(outArr)
KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
0

This should do it for you. I hope the code makes sense on its own. This example will always take the json1 value over the json2 value if both exist. If you want to change that then you need to switch the object references (src[i] and obj[j]) in the innermost loop.

// Will take src, and merge in the contents of obj.
// Expects an array of objects for both.
// Will keep src values in favour of obj values.
function extend(src, obj) {
  
  // Loop the src, in this case json1
  for (var i = 0; i < src.length; i++) {
    
    // For every loop of json1, also loop json2
    for (var j = 0; j < obj.length; j++) {
      
      // If we have matching IDs operate on this pair
      if (src[i].id == obj[j].id) {
          
        // For every key in the object being merged in,
        // if the key exists in src, ignore new value.
        // if the doesn't exist in src, take the new value.
        for (var key in obj[j]) {
          src[i][key] = src[i].hasOwnProperty(key) ? src[i][key] : obj[j][key];
        }
        
        // We found our matching pair, so break out of the json2 loop
        break;
        
      }
      
    }
    
  }
  
  return src;
}

// -------------------------------------------

var json1 = [{
  id: 1,
  name: 'aaa'
},{
  id: 5,
  name: 'ccc'
},{
  id: 3,
  name: 'bbb'
}];

var json2 = [{
  id: 3,
  parameter1: 'x', 
  parameter2: 'y', 
  parameter3: 'z'
},{
  id: 1,
  parameter1: 'u', 
  parameter2: 'v', 
  parameter3: 'w'
},{
  id: 5,
  parameter1: 'q', 
  parameter2: 'w', 
  parameter3: 'e'
}];

var json3 = extend(json1, json2);

// ---------------------------------------------

var pre = document.getElementById('out');
pre.innerHTML = JSON.stringify(json3);
<pre id="out"></pre>
Luke
  • 3,985
  • 1
  • 20
  • 35
0

Here is a generic solution using object-lib.

The advantage is that you get full control over how objects are merged and this supports nested and multiple nested groupBys.

// const objectLib = require('object-lib');

const { Merge } = objectLib;

const json1 = [{ id: 1, name: 'aaa' }, { id: 5, name: 'ccc' }, { id: 3, name: 'bbb' }];
const json2 = [{ id: 3, parameter1: 'x', parameter2: 'y', parameter3: 'z' }, { id: 1, parameter1: 'u', parameter2: 'v', parameter3: 'w' }, { id: 5, parameter1: 'q', parameter2: 'w', parameter3: 'e' }];

const groupById = Merge({ '[*]': 'id' });
console.log(groupById(json1, json2));
// => [ { id: 1, name: 'aaa', parameter1: 'u', parameter2: 'v', parameter3: 'w' }, { id: 5, name: 'ccc', parameter1: 'q', parameter2: 'w', parameter3: 'e' }, { id: 3, name: 'bbb', parameter1: 'x', parameter2: 'y', parameter3: 'z' } ]

const o1 = [{ id: 1, children: [{ type: 'A' }, { type: 'C' }] }, { id: 5 }];
const o2 = [{ id: 1, children: [{ type: 'A' }, { type: 'B' }] }, { id: 3 }];

console.log(groupById(o1, o2));
// => [ { id: 1, children: [ { type: 'A' }, { type: 'C' }, { type: 'A' }, { type: 'B' } ] }, { id: 5 }, { id: 3 } ]

const groupByCustom = Merge({
  '[*]': 'id',
  '[*].children[*]': 'type'
});
console.log(groupByCustom(o1, o2));
// => [ { id: 1, children: [ { type: 'A' }, { type: 'C' }, { type: 'B' } ] }, { id: 5 }, { id: 3 } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-lib@2.0.0"></script>

Disclaimer: I'm the author of object-lib

vincent
  • 1,953
  • 3
  • 18
  • 24
0

Stack snippets of the two one-liners

Lodash

const json1 = [{ id: 1, name: 'aaa' }, { id: 3, name: 'bbb' },
  { id: 5, name: 'ccc' }];
const json2 = [{ id: 3, parameters: 'xyz' },
  { id: 5, parameters: 'qwe' }, { id: 1, parameters: 'uvw' }];
console.log('json1 BEFORE:\n' + JSON.stringify(json1));

const result_Lodash =
  _(json1).concat(json2).groupBy('id').map(_.spread(_.assign)).value();
console.log('Result, Lodash:\n' + JSON.stringify(result_Lodash));
console.log('json1 AFTER:\n' + JSON.stringify(json1));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>

Object.assign

const json1 = [{ id: 1, name: 'aaa' }, { id: 3, name: 'bbb' },
  { id: 5, name: 'ccc' }];
const json2 = [{ id: 3, parameters: 'xyz' },
  { id: 5, parameters: 'qwe' }, { id: 1, parameters: 'uvw' }];
console.log('json2 BEFORE:\n' + JSON.stringify(json2));

const result_Object_assign =
  json2.map(x => Object.assign(x, json1.find(y => y.id === x.id)));
console.log('Object.assign:\n' + JSON.stringify(result_Object_assign));
console.log('json2 AFTER:\n' + JSON.stringify(json2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>

It is worth pointing out that for both solutions, the contents of the first / left array are changed.
Run the snippets to see it.
For example, Object.assign(x, json1.find(y => y.id === x.id)) copies the compound data to the object x in json2, and the objects in json2 are thus updated via .map().
The identifier result_Object_assign is actually nothing but another pointer that points to the very same array as json2 points to - no new object is created!


Object.assign without changing the input arrays
If you don't want to change any of the input arrays, simply create a new array, as shown below:

const json1 = [{ id: 1, name: 'aaa' }, { id: 3, name: 'bbb' },
  { id: 5, name: 'ccc' }];
const json2 = [{ id: 3, parameters: 'xyz' },
  { id: 5, parameters: 'qwe' }, { id: 1, parameters: 'uvw' }];
console.log('json2 BEFORE:\n' + JSON.stringify(json2));

const result_Object_assign =
  json2.map(x => Object.assign({}, x, json1.find(y => y.id === x.id)));
console.log('Object.assign:\n' + JSON.stringify(result_Object_assign));
console.log('json2 AFTER:\n' + JSON.stringify(json2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
Henke
  • 4,445
  • 3
  • 31
  • 44
0

Stack snippets of the concat-reduce answers

Result 1: using reduce and matching id's.
First concatenate the two arrays into a single array, then detect and combine objects with matching id's. Finally filter out all null values.

Result 2: using reduce and Object.assign.

const json1 = [{id:1,name:'aaa'},{id:3,name:'bbb'},{id:5,name:'ccc'}];
const json2 =
  [{id:3,parameters:'xyz'},{id:5,parameters:'qwe'},{id:1,parameters:'uvw'}];
const json7 = json1.concat(json2);
console.log(' json7:\n'+JSON.stringify(json7));

const Result_1 =
  json7.reduce((accumulator, obj) => {
    if (!accumulator[obj.id]) {
      accumulator[obj.id] = obj;
    } else {
      for (proprty in obj) { accumulator[obj.id][proprty] = obj[proprty];}
    }
    return accumulator;
  }, []).filter(x => x);
console.log('\n Result_1:\n' + JSON.stringify(Result_1));

const Result_2 =
  json1.concat(json2).reduce((accumulator, obj) => {
    accumulator[obj.id] = Object.assign({}, accumulator[obj.id], obj);
    return accumulator;
  }, []).filter(x => x);
console.log('\n Result_2:\n' + JSON.stringify(Result_2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

References:

Henke
  • 4,445
  • 3
  • 31
  • 44