how to set object keys to common denominators? - javascript

I have an array of objects and want every object in the array to have the same keys.
var obj1 = {"type": "test", "info": "a lot", "value": 7};
var obj2 = {"value": 5};
var obj3 = {"context": "demo", "info": "very long", "value": 3};
var obj4 = {"info": "no way"};
var dataSet = [obj1,obj2,obj3,obj4];
My attempt is to create an array with all possible keys in the first step.
Then loop through that keys array and update the objects if the key was not found.
keys.forEach(function(a,b){
dataSet.forEach(function(c,d){
//key not found
if(a in c === false)
{
//add key to object
dataSet[b][a] = false;
}
});
});
Howewer, it does not seem to work correctly.
This is my output:
after logic: [
{
"type": false,
"info": "a lot",
"value": 7
},
{
"value": 5,
"info": false
},
{
"context": "demo",
"info": "very long",
"value": false
},
{
"info": "no way",
"context": false
}
]
What am I missing there?
var obj1 = {"type": "test", "info": "a lot", "value": 7};
var obj2 = {"value": 5};
var obj3 = {"context": "demo", "info": "very long", "value": 3};
var obj4 = {"info": "no way"};
var dataSet = [obj1,obj2,obj3,obj4];
var keys = [];
console.log("before logic: ", dataSet);
//Step 1: Fill keys array
dataSet.forEach(function(a,b){
Object.keys(a).forEach(function(c,d)
{
//add keys to array if not already exists
if(!keys.includes(c))
{
keys.push(c);
}
});
});
//Step2: loop through keys array and add key to object if not existing
keys.forEach(function(a,b){
dataSet.forEach(function(c,d){
//key not found
if(a in c === false)
{
//add key to object
dataSet[b][a] = false;
}
});
});
console.log("after logic: ", dataSet);
EDIT:
It would be perfect if the keys are always sorted in the same order too.

You can just collect the keys in a Set using flatMap(), and then assign the missing ones using forEach():
const dataSet = [
{"type": "test", "info": "a lot", "value": 7},
{"value": 5},
{"context": "demo", "info": "very long", "value": 3},
{"info": "no way"}
];
const keys = new Set(dataSet.flatMap(Object.keys));
dataSet.forEach((v) => keys.forEach((k) => v[k] = k in v ? v[k] : false));
console.log(dataSet);
To address the additional request of having the keys in the same order, note that historically, JavaScript object properties were unordered, so relying on the order of object properties in JavaScript is almost never a good idea.
That being said, it's hard to get a fixed order when modifying the existing objects, but doable if you create new objects:
const dataSet = [
{"type": "test", "info": "a lot", "value": 7},
{"value": 5},
{"context": "demo", "info": "very long", "value": 3},
{"info": "no way"}
];
const keys = [...new Set(dataSet.flatMap(Object.keys))];
const result = dataSet.map((v) => keys.reduce((a, k) => ({
...a,
[k]: k in v ? v[k] : false
}), {}));
console.log(result);

You can create a 'template' object from the Set of combined keys and then simply Object.assign to this template from each object. This will give you all the properties in a consistent order.
const dataSet = [
{ "type": "test", "info": "a lot", "value": 7 },
{ "value": 5 },
{ "context": "demo", "info": "very long", "value": 3 },
{ "info": "no way" }
];
const template = Object.fromEntries([...new Set(dataSet.flatMap(o => Object.keys(o)))].map(k => [k, false]));
const result = dataSet.map(o => Object.assign({ ...template }, o));
console.log(result)
Alternatively you can create the template by simply merging all the objects in the data set and overwriting a default value.
const dataSet = [
{ "type": "test", "info": "a lot", "value": 7 },
{ "value": 5 },
{ "context": "demo", "info": "very long", "value": 3 },
{ "info": "no way" }
];
const template = Object.assign({}, ...dataSet);
for (const k of Object.keys(template)) {
template[k] = false;
}
const result = dataSet.map(o => Object.assign({ ...template }, o));
console.log(result)

The problem with your code is basically a typo: You're using the wrong index when you set the key:
dataSet[b][a] = false;
b is the index of the key in keys, not the index of the object in dataSet. You don't need to do that indexing at all, just do:
c[a] = false;
It's much easier to follow what you're doing and such when you use meaningful names for variables rather than a, b, c, and d. Here's your code with some reasonable renaming and with the change described above:
var obj1 = { type: "test", info: "a lot", value: 7 };
var obj2 = { value: 5 };
var obj3 = { context: "demo", info: "very long", value: 3 };
var obj4 = { info: "no way" };
var dataSet = [obj1, obj2, obj3, obj4];
var keys = [];
console.log("before logic: ", JSON.stringify(dataSet, null, 4));
//Step 1: Fill keys array
dataSet.forEach(function (obj) {
Object.keys(obj).forEach(function (key) {
//add keys to array if not already exists
if (!keys.includes(key)) {
keys.push(key);
}
});
});
//Step2: loop through keys array and add key to object if not existing
keys.forEach(function (key) {
dataSet.forEach(function (obj) {
//key not found
if (key in obj === false) {
//add key to object
obj[key] = false;
}
});
});
console.log("after logic: ", JSON.stringify(dataSet, null, 4));
.as-console-wrapper {
max-height: 100% !important;
}
But that code can be much simpler using a Set and modern language features:
const obj1 = { type: "test", info: "a lot", value: 7 };
const obj2 = { value: 5 };
const obj3 = { context: "demo", info: "very long", value: 3 };
const obj4 = { info: "no way" };
const dataSet = [obj1, obj2, obj3, obj4];
const keys = new Set(); // *** Use a set
console.log("before logic: ", dataSet);
// Step 1: Fill keys array
for (const obj of dataSet) {
for (const key of Object.keys(obj)) {
keys.add(key);
}
}
// Step2: loop through keys array and add key to object if not existing
for (const key of keys) {
for (const obj of dataSet) {
if (!(key in obj)) {
obj[key] = false;
}
}
}
console.log("after logic: ", JSON.stringify(dataSet, null, 4));
.as-console-wrapper {
max-height: 100% !important;
}
Robby Cornelissen takes it much further, but I wanted to show using simple loops.

Related

Removing unwanted object keys & undefined in Javascript?

This is my array:
const
array1 = [
{
"value": "0",
"name": "5",
"waste": "remove",
"city": "NY"
},
{
"value": "0",
"name": "51",
"waste": "remove",
}
]
So now, i wanted to remove certain and form a new array with objects: For example, i need to remove "Waste & value" and keep rest of the things, so i used this code:
var keys_to_keep = ['name', 'city']
const result = array2.map(e => {
const obj = {};
keys_to_keep.forEach(k => obj[k] = e[k])
return obj;
});
console.log(result)
And it gives a output as
[ { name: '5', city: 'NY' }, { name: '51', city: undefined } ]
Now as you can see city with undefined value, how to remove that ? i mean filter this and just show keys with value,
So my question is how to filter undefined and also is there any other better solution for removing unwanted object keys and showing new array with wanted keys ? or the method am using is performant enough ?
You can check if the value is undefined in your forEach:
const result = array2.map(e => {
const obj = {};
keys_to_keep.forEach(k => {
if (undefined !== e[k]) {
obj[k] = e[k]
}
)
return obj;
});
You can check if e[k] is defined before you add it to obj by checking whether the e object has the property k using .hasOwnProperty():
const array = [{ "value": "0", "name": "5", "waste": "remove", "city": "NY" }, { "value": "0", "name": "51", "waste": "remove", } ];
const keys_to_keep = ['name', 'city'];
const result = array.map(e => {
const obj = {};
keys_to_keep.forEach(k => {
if (e.hasOwnProperty(k))
obj[k] = e[k]
});
return obj;
});
console.log(result)
If the keys you want to remove aren't dynamic, you can also use destructuring assignment to pull out the properties you want to discard, and use the rest syntax to obtain an object without those properties:
const array = [{ "value": "0", "name": "5", "waste": "remove", "city": "NY" }, { "value": "0", "name": "51", "waste": "remove", } ];
const result = array.map(({value, waste, ...r}) => r);
console.log(result)
I am going to answer both the parts. So here are the steps to do that.
Use map() on the main array.
Get entries of each object using Object.entries().
Apply filter() on entires array are remove those entires for which key is not present in keys_to_keep
Now for the second part.
Using keys_to_keep create an object which contain undefined values for each key.
Use map() again on prev result and use Spread operator. First spread the object created above and then spread the original values. This way if any key is not found it will be set to undefined
const
array1 = [
{
"value": "0",
"name": "5",
"waste": "remove",
"city": "NY"
},
{
"value": "0",
"name": "51",
"waste": "remove",
}
]
var keys_to_keep = ['name', 'city']
let obj = Object.fromEntries(keys_to_keep.map(x => [x, undefined]));
const res = array1.map(obj =>
Object.fromEntries(
Object.entries(obj).filter(([k, v]) => keys_to_keep.includes(k))))
.map(x => ({...obj, ...x}))
console.log(res)
You can use .map to iterate over the objects, Object.entries to get the key-value pairs of each item, Object.fromEntries to group them into the resulting objects, and .filter to get only the entries with a key in keys_to_keep and a value that is not undefined:
const array1 = [
{ "value": "0", "name": "5", "waste": "remove", "city": "NY" },
{ "value": "0", "name": "51", "waste": "remove" }
];
var keys_to_keep = ['name', 'city'];
const result = array1.map(item =>
Object.fromEntries(
Object.entries(item).filter(([key, value]) =>
keys_to_keep.includes(key) && value !== undefined
)
)
);
console.log(result)

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?
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);
You need to identify objects with the same gameId, and then concat and dedupe their _.scores array.
It's easy to concat/dedup non primitive array items using Array.reduce() and a Map. For every item you check if the requested key is already in the Map. If it's not, you assign the current item to the Map's key. If it is you replace / merge the current item with the item in the Map.
After you finish iterating the Map, use Array.from() to convert the Map's .values() iterator to an array.
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 dedupLastBy = (a1 = [], a2 = [], key) => Array.from(
[...a1, ...a2].reduce((acc, obj) => {
const keyName = obj[key];
if(acc.has(keyName)) acc.delete(keyName);
return acc.set(keyName, obj);
}, new Map()).values()
)
const handleDups = ({ _scores: a, ...o1 }, { _scores: b, ...o2 }) => ({
...o1,
...o2,
_scores: dedupLastBy(a, b, 'playerName')
});
const result = Array.from([...arr1, ...arr2]
.reduce((acc, o) => {
const { gameId } = o;
if(acc.has(gameId)) acc.set(gameId, handleDups(acc.get(gameId), o));
else acc.set(gameId, o);
return acc;
}, new Map()).values());
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

Unable to convert a nested object into an array of objects and viceversa

I am having an object of the following kind:
var sourceObj = {
"feature1": [
{"id":"1","name":"abc","enabled":false,"type":"type1"},
{"id":"2","name":"xyz","enabled":false,"type":"type1"}
]
,
"feature2": [
{"id":"3","name":"lmn","enabled":true,"type":"type2"},
{"id":"4","name":"pqr","enabled":false,"type":"type2"}
]
}
Need to get converted to an array of objects of the following type:
var destArr = [
{ "feature_name":"feature1",
"feature_details":[
{"id":"1","name":"abc","enabled":false,"type":"type1"},
{"id":"2","name":"xyz","enabled":true,"type":"type1"}
]
},
{ "feature_name":"feature2",
"feature_details":[
{"id":"3","name":"lmn","enabled":true,"type":"type2"}
{"id":"4","name":"pqr","enabled":false,"type":"type2"}
]
}
]
I have tried the following approaches for conversion of source object to resultant array of objects and resultant array of objects back to the source object
//Converting source object to array of objects
let arr = Object.keys(sourceObj).map(key => {
return sourceObj[key];
})
converting array of objects back to source objetc
let obj = Object.assign({}, ...destArr.map(item => ({ [item.name]: item.value })));
You could use Object.entries to map everything in a single shot.
To go back to the original structure, you could use reduce against the generated array (see backToOriginalArray below)
var sourceObj = {
"feature1": [{
"id": "1",
"name": "abc",
"enabled": false,
"type": "type1"
},
{
"id": "2",
"name": "xyz",
"enabled": false,
"type": "type1"
}
],
"feature2": [{
"id": "3",
"name": "lmn",
"enabled": true,
"type": "type2"
},
{
"id": "4",
"name": "pqr",
"enabled": false,
"type": "type2"
}
]
};
// Step 1: use object.entries against the original object to build an array of objects.
var destArray = Object.entries(sourceObj).map(([key, value]) => ({
"feature_name": key,
"feature_details": value
}));
console.log(destArray);
// Step 2: use reduce against the generated array to get an object with the same structure of the original one.
var backToOriginalArray = destArray.reduce((acc, {feature_name, feature_details}) => (acc[feature_name] = feature_details, acc), {});
console.log(backToOriginalArray);
Map the Object.entries of your initial object:
var sourceObj = {
"feature1": [
{"id":"1","name":"abc","enabled":false,"type":"type1"},
{"id":"2","name":"xyz","enabled":false,"type":"type1"}
],
"feature2": [
{"id":"3","name":"lmn","enabled":true,"type":"type2"},
{"id":"4","name":"pqr","enabled":false,"type":"type2"}
]
};
const destArr = Object.entries(sourceObj).map(
([feature_name, feature_details]) => ({ feature_name, feature_details })
);
console.log(destArr);
To go the other way around, use Object.fromEntries:
const arr=[{feature_name:"feature1",feature_details:[{id:"1",name:"abc",enabled:!1,type:"type1"},{id:"2",name:"xyz",enabled:!1,type:"type1"}]},{feature_name:"feature2",feature_details:[{id:"3",name:"lmn",enabled:!0,type:"type2"},{id:"4",name:"pqr",enabled:!1,type:"type2"}]}];
const obj = Object.fromEntries(arr.map(
({ feature_name, feature_details }) => [feature_name, feature_details]
));
console.log(obj);

How to parse json key dot literals

I have this JSON object, it's a dynamically generated object, I can't control how it's formatted to json. The problem is that the json looks like this:
{
"id": "def",
"name": "def",
"description": {},
"description.shortened": "def",
"description.extended": "def",
"type": "EDIBLE_BOUQUET",
"image": {},
"image.name": "def",
"image.slug": "def",
"image.extension": "PNG",
"state": "FEATURED",
"stock": "def"
}
How would I go about transforming it into:
{
"id": "def",
"name": "def",
"description": {
"shortened": "def",
"extended": "def"
},
"type": "EDIBLE_BOUQUET",
"image": {
"name": "def",
"slug": "def",
"extension": "PNG"
},
"state": "FEATURED",
"stock": "def"
}
let obj = {
"id": "def",
"name": "def",
"description": {},
"description.shortened": "def",
"description.extended": "def",
"type": "EDIBLE_BOUQUET",
"image": {},
"image.name": "def",
"image.slug": "def",
"image.extension": "PNG",
"state": "FEATURED",
"stock": "def"
}
const transformObj = obj => {
return Object.keys(obj).reduce((acc, key) => {
if(key.indexOf('.') >= 0){
const [parentKey, childKey] = key.split('.');
acc[parentKey] = acc[parentKey] || {};
acc[parentKey][childKey] = obj[key];
} else {
acc[key] = obj[key];
}
return acc;
}, {});
}
console.log(transformObj(obj));
Iterate through the keys of object, and transform it.
function transform(obj){
let tmp={}
Object.keys(obj).forEach(k=>{
if(k.includes('.')){
let path=k.split('.')
let x=path.pop()
path.reduce((cur,p)=>{
if(!(p in cur))cur[p]={}
return cur[p]
},tmp)[x]=obj[k]
}
else{
tmp[k]=obj[k]
}
})
return tmp
}
const obj={
a: 1,
'b.c.d': 2
}
console.log(transform(obj)) // {a: 1, b: {c: {d: 2}}}
If you're in for using lodash, _.set does exactly what you're trying to do here:
const source = {"id":"def","name":"def","description":{},"description.shortened":"def","description.extended":"def","type":"EDIBLE_BOUQUET","image":{},"image.name":"def","image.slug":"def","image.extension":"PNG","state":"FEATURED","stock":"def"};
let target = {};
Object.keys(source).forEach(key => {
_.set(target, key, source[key]);
});
console.log(target);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
Just one catch, the parent property should always be before the child properties in your source object, i.e. description should occur before description.shortened.
Fairly straight forward.
Check each property to see if the name contains a ., add the correct property and delete the original.
const src = {
"id": "def",
"name": "def",
"description": {},
"description.shortened": "def",
"description.extended": "def",
"type": "EDIBLE_BOUQUET",
"image": {},
"image.name": "def",
"image.slug": "def",
"image.extension": "PNG",
"state": "FEATURED",
"stock": "def"
};
for (var k in src) {
let index = k.indexOf('.');
if (index > 0){
let [base, prop] = k.split('.');
let value = src[k];
delete src[k]
src[base][prop] = value;
}
}
console.log(src)
Using lodash's set method would be the easiest path.
const json = '{"id": "def","name": "def","description": {},"description.shortened": "def","description.extended": "def","type": "EDIBLE_BOUQUET","image": {},"image.name": "def","image.slug": "def","image.extension": "PNG","state": "FEATURED","stock": "def"}';
const obj = JSON.parse(json);
const newObj = Object.keys(obj).reduce((o, k) => _.set(o, k, obj[k]), {});
console.log(newObj);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
A concern should be noted that if the object keys are not guaranteed to be in the order shown, some data may be lost. See the following example.
const json = '{"id": "def","name": "def","description.shortened": "def","description.extended": "def","description": {},"type": "EDIBLE_BOUQUET","image": {},"image.name": "def","image.slug": "def","image.extension": "PNG","state": "FEATURED","stock": "def"}';
const obj = JSON.parse(json);
const newObj = Object.keys(obj).reduce((o, k) => _.set(o, k, obj[k]), {});
console.log(newObj);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
Since the set method iterates over the keys in the order provided from the keys method, the last key will set the value to whatever is provided. So, if the description: {} pair follows any previous description.x: y pair then those values will be lost once the empty object is assigned.
A simple fix would be to include a sort to force the empty object pair to be the first key provided. Note however this will also cause the rest of the object to no longer be in the same order as the original.
const json = '{"id": "def","name": "def","description.shortened": "def","description.extended": "def","description": {},"type": "EDIBLE_BOUQUET","image": {},"image.name": "def","image.slug": "def","image.extension": "PNG","state": "FEATURED","stock": "def"}';
const obj = JSON.parse(json);
const newObj = Object.keys(obj).sort().reduce((o, k) => _.set(o, k, obj[k]), {});
console.log(newObj);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
If you do need to roll your own then something like the following would suffice:
const json = '{"id": "def","name": "def","description.shortened": "def","description.extended": "def","description": {},"type": "EDIBLE_BOUQUET","image": {},"image.name": "def","image.slug": "def","image.extension": "PNG","state": "FEATURED","stock": "def"}';
const obj = JSON.parse(json);
const newObj = Object.keys(obj).sort().reduce((o, k) => {
const paths = k.split('.');
// Get to currently defined depth of object
let depth = 0;
let oRef = o;
while (oRef.hasOwnProperty(paths[depth])) {
oRef = oRef[paths[depth++]];
}
const val = paths.slice(depth).reduceRight((v, p) => ({[p]: v}), obj[k]);
Object.assign(oRef, val);
return o;
}, {});
console.log(newObj);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>

How to merge objects with the same properties into an Array?

I would like to merge 2 objects with the same properties into an Array.
Take this for an example:
object1 = {"id":1,
"name":name1,
"children":[{"id":2,"name":name2}]
};
object2 = {"id":3,
"name":name3,
"children":[{"id":4,"name":name4}]
};
object3 = {"id":1,
"name":name1,
"children":[{"id":6,"name":name6}]
};
var result = Object.assign(result,object1,object2,object3);
Expected result:
JSON.stringify([result]) =[
{"id":1,
"name":name1,
"children":[{"id":2,"name":name2},
{"id":6,"name":name6}]
},
{"id":3,
"name":name3,
"children":[{"id":4,"name":name4}]
}
]
Actual result:
JSON.stringify([result]) = [
{"id":3,
"name":name3,
"children":[{"id":4,"name":name4}]
}
]
Seems like Object.assign() isn't the way to go... as it will overwrite, I do not want it to overwrite, I want them to merge instead. Is there a right way to do this?
As so often, Array.prototype.reduce provides a good base for an approach like e.g. this one ...
var obj1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2" }]
};
var obj2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4" }]
};
var obj3 = {
"id": 1,
"name": "name1",
"children": [{ "id": 6, "name": "name6" }]
};
// Expected result: [{
// "id": 1,
// "name": name1,
// "children": [
// { "id": 2, "name": "name2" },
// { "id": 6, "name": "name6" }
// ]
// }, {
// "id": 3,
// "name": "name3",
// "children": [{"id": 4, "name": "name4" }]
// }]
function mergeEquallyLabeledTypes(collector, type) {
var key = (type.name + '#' + type.id); // identity key.
var store = collector.store;
var storedType = store[key];
if (storedType) { // merge `children` of identically named types.
storedType.children = storedType.children.concat(type.children);
} else {
store[key] = type;
collector.list.push(type);
}
return collector;
}
var result = [obj1, obj2, obj3].reduce(mergeEquallyLabeledTypes, {
store: {},
list: []
}).list;
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Edit Note
After having been informed about changed requirements, that need to deal with a nested pattern, I will change my first provided approach into a generic solution. It will be not that difficult since there is a generically repeated pattern within the data structure. Thus I just need to make the already existing reducer function self recursive. A recursion step will be triggered after having finished a complete reducing cycle on any provided list ...
var obj1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2", "children": [{ "id": 8, "name": "name8" }] }]
};
var obj2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", "children": [{ "id": 9, "name": "name9" }] }]
};
var obj3 = {
"id": 1,
"name": "name1",
"children": [{ "id": 6, "name": "name6", "children": [{ "id": 10, "name": "name10" }] }]
};
var obj4 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", "children": [{ "id": 11, "name": "name11" }] }]
};
function mergeEquallyLabeledTypesRecursively(collector, type, idx, list) {
var key = (type.name + '#' + type.id); // identity key.
var store = collector.store;
var storedType = store[key];
if (storedType) { // merge `children` of identically named types.
storedType.children = storedType.children.concat(type.children);
} else {
store[key] = type;
collector.list.push(type);
}
// take repetitive data patterns into account ...
if (idx >= (list.length - 1)) {
collector.list.forEach(function (type) {
// ... behave recursive, when appropriate.
if (type.children) {
type.children = type.children.reduce(mergeEquallyLabeledTypesRecursively, {
store: {},
list: []
}).list;
}
});
}
return collector;
}
var result = [obj1, obj2, obj3, obj4].reduce(mergeEquallyLabeledTypesRecursively, {
store: {},
list: []
}).list;
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
This might be what your after, please note it's not recursive now recursive. But your example data doesn't appear to be anyway.
const object1 = {"id":1,
"name":"name1",
"children":[{"id":2,"name":"name2"}]
};
const object2 = {"id":3,
"name":"name3",
"children":[{"id":4,"name":"name4"}]
};
const object3 = {"id":1,
"name":"name1",
"children":[
{"id":6,"name":"name6"},
{"id":7,"name":"name7"},
{"id":6,"name":"name6"}
]
};
function merge(arr) {
const idLinks = {};
const ret = [];
arr.forEach((r) => {
if (!idLinks[r.id]) idLinks[r.id] = [];
idLinks[r.id].push(r);
});
Object.keys(idLinks).forEach((k) => {
const nn = idLinks[k];
const n = nn[0];
for (let l = 1; l < nn.length; l ++) {
if (nn[l].children) {
if (!n.children) n.children = [];
n.children = n.children.concat(nn[l].children);
}
}
if (n.children && n.children.length) n.children = merge(n.children);
ret.push(n);
});
return ret;
}
var result = merge([object1,object2,object3]);
console.log(result);
/* There are two cases :
a) No duplicate children
b) Duplicate children either in (same object || different object|| both)
*/
/* =============== */
/* Case a) */
const util = require('util');
var object1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2" }]
};
var object2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4" }]
};
var object3 = {
"id": 1,
"name":"name1",
"children":[{"id":6,"name":"name6"}]
};
var arr = [object1,object2,object3];
var uniqueIds = [];
var filteredArray = [];
var uniqueId='';
arr.map((item,i,array)=>{
uniqueId =uniqueIds.indexOf(item.id);
uniqueId = uniqueId+1;
uniqueIds = [...uniqueIds,item.id];
if(!uniqueId){
filteredArray[i] = item;
}
if(uniqueId){
filteredArray[uniqueId-1]['children'] = [...(array[uniqueId-1].children),...(item.children)];
}
});
console.log(util.inspect(filteredArray,false,null));
/* ============================================
Case b)
Dealing with the worst case of having duplicate children in both same
and different objects
*/
object1 = {"id":1,
"name":'name1',
"children":[{"id":2,"name":'name2'},
{"id":2,"name":'name2'}]
};
object2 = {"id":3,
"name":'name3',
"children":[{"id":4,"name":'name4'}]
};
object3 = {"id":1,
"name":'name1',
"children":[{"id":6,"name":'name6'},
{"id":7,"name":'name7'},
{"id":2,"name":'name2'}]
};
arr = [object1,object2,object3];
uniqueIds = [];
uniqueId = '';
arr.map((item,i,array)=>{
uniqueId =uniqueIds.indexOf(item.id);
uniqueId = uniqueId+1;
uniqueIds = [...uniqueIds,item.id];
if(!uniqueId){
filteredArray[i] = item;
}
if(uniqueId){
filteredArray[uniqueId-1]['children'] = [...(array[uniqueId-1].children),...(item.children)];
}
/*Removing duplicate children entries*/
filteredArray[uniqueIds.indexOf(item.id)]['children'] = filteredArray[uniqueIds.indexOf(item.id)]['children']
.filter((elem, index, self) => self.findIndex((t) => {return t.id === elem.id}) === index)
})
console.log(util.inspect(filteredArray,false,null));
In functional programming way with es6 standards. I am assuming children array also contains duplicates. I enclosed the code in closures.
See the following link why I used util to print all the object in node console.log()
How can I get the full object in Node.js's console.log(), rather than '[Object]'?
(function() {
'use strict';
const util = require('util');
/** string constants */
const ID = 'id';
const CHILDREN = 'children';
/* Objects to modify */
const object1 = {
"id": 1,
"name": "name1",
"children": [
{ "id": 2, "name": "name2" },
{ "id": 5, "name": "name5" },
{ "id": 7, "name": "name7" }
]
};
const object2 = {
"id": 3,
"name": "name3",
"children": [
{ "id": 4, "name": "name4" }
]
};
const object3 = {
"id": 1,
"name": "name1",
"children": [
{ "id": 5, "name": "name5" },
{ "id": 6, "name": "name6" }
]
};
/**
* Concates the arrays
* #param { array } - a
* #param { array } - b
*/
const merge = (a, b) => {
return a.concat(b);
};
/**
* Removes Duplicates from the given array based on ID
* #param { array } - array to remove duplicates
* #return { array } - array without duplicates
*/
const removeDuplicates = (arr) => {
return arr.filter((obj, pos, arr) => {
return arr.map((m) => {
return m[ID];
}).indexOf(obj[ID]) === pos;
});
}
/**
* Groups items in array with particular key
* Currying technique
* #param { prop } - key to group
* #return { () => {} } - Method which in turn takes array as argument
*/
const groupBy = (prop) => (array) => {
return array.reduce((groups, item) => {
const val = item[prop];
groups[val] = groups[val] || [];
groups[val].push(item);
return groups;
}, {});
}
/**
* Object containing grouped-items by particuar key
*/
const grouped = groupBy(ID)([object1, object2, object3]);
/**
* Removing the duplicates of children
* Remember map also mutates the array of objects key's value
* but not data type
*/
Object.keys(grouped).map((key, position) => {
grouped[key].reduce((a, b) => {
a[CHILDREN] = removeDuplicates(a[CHILDREN].concat(b[CHILDREN]));
});
});
/**
* Desired final output
*/
const final = Object.keys(grouped)
.map((key) => removeDuplicates(grouped[key]))
.reduce(merge, []);
console.log(util.inspect(final, false, null))})();
const object1 = {
"id":1,
"name":"name1",
"children":[{"id":2,"name":"name2"}]
};
const object2 = {
"id":3,
"name":"name3",
"children":[{"id":4,"name":"name4"}]
};
const object3 = {
"id":1,
"name":"name1",
"children":[{"id":6,"name":"name6"}]
};
var array = [object1,object2,object3];
var array2 = [object1,object2,object3];
function uniquearray(obj){
var result =[];
for(var i=0;i<array.length;i++){
if(obj.id == array[i].id){
result.push(array[i])
array.splice(i,1)
}
}
return result;
}
var arrayofuniarrays = []
for(var i=0;i<array2.length;i++){
arrayofuniarrays.push(uniquearray(array2[i]))
}
for(var i=0;i<arrayofuniarrays.length;i++){
for(var j=1;j<arrayofuniarrays[i].length; j++){
arrayofuniarrays[i][0].children.push(arrayofuniarrays[i][j].children)
arrayofuniarrays[i].splice(j,1)
}
}
var resul = arrayofuniarrays.reduce(function(a, b){return a.concat(b)},[])
console.log(resul)
Here is a sketch example of how to do this. It leverages a mapped type using your id as a key to ensure each item only appears once. It adds all of the children to an array based on the id.
If you needed to enforce the same behaviour on the children, you could use the same technique.
I have split this into multiple iterations to show you the individual parts in play.
Usually, it is more efficient to avoid creating objects that need to be zipped back up if you can.
const object1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2" }]
};
const object2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4" }]
};
const object3 = {
"id": 1,
"name":"name1",
"children":[{"id":6,"name":"name6"}]
};
const all = [object1, object2, object3];
// Use a map like a dictionary to enforce unique keys
const mapped = {};
for (let obj of all) {
if (!mapped[obj.id]) {
mapped[obj.id] = obj;
continue;
}
mapped[obj.id].children.push(obj.children);
}
console.log('Mapped ==> '+JSON.stringify(mapped));
// If you want to convert the mapped type to an array
const result = [];
for (let key in mapped) {
result.push(mapped[key]);
}
console.log('Array ==> '+JSON.stringify(result));
Building on #Peter Seliger's answer here, I derived with the following method to merge arrays with deeply nested children.
Given the following objects:
var obj1 = {
"id": 1,
"name": "name1",
"children": [{ "id": 2, "name": "name2", children:[{ "id":8, "name": "name8" }] }]
};
var obj2 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", children:[{ "id":9, "name": "name9" }] }]
};
var obj3 = {
"id": 1,
"name": "name1",
"children": [{ "id": 6, "name": "name6", children:[{ "id":10, "name": "name10" }] }]
};
var obj4 = {
"id": 3,
"name": "name3",
"children": [{ "id": 4, "name": "name4", children:[{ "id":11, "name": "name11" }] }]
};
First we merge the parents
function mergeEquallyLabeledTypes(collector, type) {
var key = (type.name + '#' + type.id); // identity key.
var store = collector.store;
var storedType = store[key];
if (storedType) { // merge `children` of identically named types.
if(storedType.children)
storedType.children = storedType.children.concat(type.children);
} else {
store[key] = type;
collector.list.push(type);
}
return collector;
}
var result = [obj1, obj2, obj3, obj4].reduce(mergeEquallyLabeledTypes, {
store: {},
list: []
}).list;
Then we merge the children and subchildren if any.
for(let i=0; i<result.length; i++){
var children = result[i].children;
if(children){
var reducedChildren = children.reduce(mergeEquallyLabeledTypes, {store: {}, list: []}).list;
for(let j=0; j<reducedChildren.length; j++){
var subchildren = reducedChildren[j].children;
if(subchildren){
var reducedSubchildren = subchildren.reduce(mergeEquallyLabeledTypes, {store: {}, list: []}).list;
reducedChildren[j].children = reducedSubchildren;
}
}
result[i].children = reducedChildren;
}
}
Finally the result will be what I'll parse into my website.
console.log('result : ', result);
I am able to get the expected result.
// result: [{
// "id": 1,
// "name": name1,
// "children": [
// { "id": 2, "name": "name2", children:[{ "id":8, "name": "name8" }] },
// { "id": 6, "name": "name6", children:[{ "id":10, "name": "name10" }] }
// ]
// }, {
// "id": 3,
// "name": "name3",
// "children": [{"id": 4, "name": "name4", children:[
// { "id":9, "name": "name9" },
// { "id":11, "name": "name11" }
// ]
// }
// ]
// }]
However, this might not be too efficient as I'll need to keep adding on to the merging of children/subchildren method if my tree get nested with more levels. (e.g. subsubchildren, subsubsubchildren and so on...)
Is there any more efficient way to do this?
const object1 = {
id:1,
name:'a',
}
const object2 = {
id:3,
name:'b',
}
const object3 = {
id:1,
name:'c',
}
const originArr = [object1, object2, object3]
const idArr = [object1.id, object2.id, object3.id]
const newIdArr = []
for (let id of idArr) {
if (newIdArr.indexOf(id)) newIdArr.push(id)
}
const result = newIdArr.map(id => {
let names = []
for (obj of originArr) {
if (id === obj.id) names.push(obj.name)
}
return { id, names }
})
console.log(result)

Categories

Resources