How to place others name object to last position of the array? - javascript

I have a object of array which is give below -
arrayData = [
{label:"data",value:"data"},
{label:"data",value:"data"},
{label:"Others",value:"Others"},
{label:"data",value:"data"},
]
and want to shift the others object at the last position of the array and i also don't know the index value of that object.
Thankyou

You can filter it out first and push then into the end:
var arrayData = [
{label:"data",value:"data"},
{label:"data",value:"data"},
{label:"Others",value:"Others"},
{label:"data",value:"data"},
]
var res = arrayData.filter(x => x.label !== 'Others');
res.push(arrayData.find(x => x.label === 'Others'));
console.log(res);

i also don't know the index value of that object
for that you can use findIndex. First get the index of the required object , and in another variable get the last object in the array. Then swap their place
var arrayData = [{
label: "data",
value: "data"
},
{
label: "data",
value: "data"
},
{
label: "Others",
value: "Others"
},
{
label: "data",
value: "data"
},
]
let findIndex = arrayData.findIndex(function(item) {
return item.label === 'Others' && item.value === 'Others'
})
let storeLastVal = arrayData[arrayData.length - 1];
let otherVal = arrayData[findIndex]
arrayData[findIndex] = storeLastVal;
arrayData[arrayData.length - 1] = otherVal;
console.log(arrayData)

You could get the index first, splice this item and push the spreaded array.
var array = [{ label: "data", value: "data" }, { label: "data", value: "data" }, { label: "Others", value: "Others" }, { label: "data", value: "data" }],
index = array.findIndex(({ value }) => value === 'Others');
array.push(...array.splice(index, 1));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Well you can first parse the array and create two arrays, one with the elements that will stay in place, the other with the elements that will be placed at the end. Then you can join those 2 arrays.
arrayData = [
{label:"data",value:"data"},
{label:"data",value:"data"},
{label:"Others",value:"Others"},
{label:"data",value:"data"},
]
var startValues = [];
var endValues = [];
arrayData.forEach((value) => {
if (value.label === "Others") {
endValues.push(value);
} else {
startValues.push(value);
}
});
arrayData = startValues.concat(endValues);

Or you can use recursion. This program has a distinct advantage in that it doesn't have to find the element (or index) before beginning the computation -
const data =
[ { label: "data", value: "data" }
, { label: "data", value: "data" }
, { label: "Others", value: "Others" }
, { label: "data", value: "data" }
]
const transform = ([ a, ...rest ]) =>
a === undefined
? []
: a.label === "Others"
? [ ...transform (rest), a ]
: [ a, ...transform (rest) ]
console .log (transform(data))
// [ { label: "data", value: "data" }
// , { label: "data", value: "data" }
// , { label: "data", value: "data" }
// , { label: "Others", value: "Others" }
// ]
Obviously you should probably make "Others" a parameter of the function -
const data =
[ { label: "data", value: "data" }
, { label: "data", value: "data" }
, { label: "Others", value: "Others" }
, { label: "data", value: "data" }
]
const transform = (query, [ a, ...rest ]) =>
a === undefined
? []
: a.label === query
? [ ...transform (query, rest), a ]
: [ a, ...transform (query, rest) ]
console .log (transform ("Others", data))
// [ { label: "data", value: "data" }
// , { label: "data", value: "data" }
// , { label: "data", value: "data" }
// , { label: "Others", value: "Others" }
// ]
console .log (transform ("data", data))
// [ { label: "Others", value: "Others" }
// , { label: "data", value: "data" }
// , { label: "data", value: "data" }
// , { label: "data", value: "data" }
// ]
Note, the original input is not mutated. If multiple labels are found matching the query, all of them will be moved to the end of the array.

Related

How to filter array of array objects?

Trying to filter array of array objects. when variable matches with array of object value of dropDownOne key 'filterValue', then it will return dropDownTwo array,
let testName = ‘ filterValue’
var nestedArray = [
[
{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}
],
[
{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [
{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}
]
]
Output =
dropDownTwo:[
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
Tried with this
let filterObj = nestedArray.filter((arr => arr.filter(value => {
if (value[0].dropDownOne.key === 'filterValue') {
return arr[1];
}
}))
But did not get the correct result
We can use Array.flat() and Array.flatMap() combined with Array.filter() to do it
let result = nestedArray.flat().filter(e => e.dropDownOne.key === testName).flatMap(e => e.dropDownTwo)
console.log(result)
let testName = `filterValue`
var nestedArray = [
[
{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}
],
[
{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [
{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}
]
]
let result = nestedArray.flat().filter(e => e.dropDownOne.key === testName).flatMap(e => e.dropDownTwo)
console.log(result)
You can do it in this way :
nestedArray.filter(arr => arr.find(item=> item.dropDownOne.key === 'filterValue'))[0][0].dropDownTwo
var nestedArray = [
[
{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [
{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}
],
[
{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [
{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}
]
]
const result = nestedArray.filter(arr => arr.find(item=> item.dropDownOne.key === 'filterValue'))[0][0].dropDownTwo
console.log(result)
You could use both flat() to flatten the nestedArray, then use the find() method to find the object with a dropDownOne.key that matches the value of testName.
it should then return the dropDownTwo property of that object.
let result = nestedArray.flat().find(obj => obj.dropDownOne.key === testName);
let filterObj = result ? result.dropDownTwo : [];
console.log(filterObj);
a full running example:
let testName = 'filterValue';
var nestedArray = [
[{
dropDownOne: {
key: "filterValue",
value: "test1"
},
dropDownTwo: [{
key: "retrieveArrKey1",
value: "test123"
},
{
key: "retrieveArrKey2",
value: "test345"
}
]
}],
[{
dropDownOne: {
key: "NoFilter",
value: "test2"
},
dropDownTwo: [{
key: "dropDown2",
value: "test"
},
{
key: "dropDown3",
value: "test"
}
]
}]
]
let result = nestedArray.flat().find(obj => obj.dropDownOne.key === testName);
let filterObj = result ? result.dropDownTwo : [];
console.log(filterObj);
Don't like other solutions due to too many array iterations. You could make a single run using reduce function which is a grandfather of many JS Array functions. It looks a bit worse than flat-filter-flat-..., but will probably work way faster due to much lower array iterations.
const testName = 'filterValue';
const nestedArray = [
[{
dropDownOne: {key: 'filterValue', value: 'test1'},
dropDownTwo: [
{key: 'retrieveArrKey1', value: 'test123'},
{key: 'retrieveArrKey2', value: 'test345'},
],
}],
[{
dropDownOne: {key: 'NoFilter', value: 'test2'},
dropDownTwo: [
{key: 'dropDown2', value: 'test'},
{key: 'dropDown3', value: 'test'},
],
}],
];
const result = nestedArray.reduce((acc, nestedItem) => {
nestedItem.forEach(item => {
if (item.dropDownOne.key === testName) {
acc.push(item.dropDownTwo);
}
});
return acc;
}, []);
console.log(result);

Javascript: Check if duplicate key exist, Add corresponding children for the duplicate key

I am trying to figure out a sample array of object in which I have following key value pair. I need to find key which I am splitting based on underscore, first splitted value will become key and second will become the array of object of that key. I am getting duplicate key which needs to be unique and then add values into it.
const arr = [
{label: 'id', key: 'wfc_id'},
{label: 'Name', key: 'wfc_name'},
{label: 'Age', key: 'wfc_age'},
{label: 'id', key: 'ga_id'},
{label: 'Name', key: 'ga_name'},
{label: 'Age', key: 'ga_age'},
{label: 'Name', key: 'rtc_name'},
{label: 'id', key: 'rtc_id'},
]
Desired Ouput:
output = {
wfc: {id:true, name:true, age: true},
ga: {id:true, name:true, age: true},
rtc: {id:true, name:true},
}
I tried following code:
let output = Object.assign({},arr.map((item) => {
let str = item.key.split('_');
let obj = {};
obj[str[0]] = {
[str[1]]: true
}
return obj
})
);
console.log(output);
But it giving me output as
{
"0": {
"wfc": {
"id": true
}
},
"1": {
"wfc": {
"name": true
}
},
"2": {
"wfc": {
"age": true
}
},
"3": {
"ga": {
"id": true
}
},
"4": {
"ga": {
"name": true
}
},
"5": {
"ga": {
"age": true
}
},
.......
}
I require if key already exits then add array/object for it's corresponding key
The map() function returns a new array. To transform the output, you need to reduce(), also called a "fold."
arr.reduce((acc, curr) => {
const split = curr.key.split('_');
const identifier = split[0];
const property = split[1];
acc[identifier] = { ...acc[identifier], [property]: true };
return acc;
}, {});
You're better off using reduce in this case. You present it with an initial object (accumulator), and then add to it over the iterations.
const arr = [
{label: 'id', key: 'wfc_id'},
{label: 'Name', key: 'wfc_name'},
{label: 'Age', key: 'wfc_age'},
{label: 'id', key: 'ga_id'},
{label: 'Name', key: 'ga_name'},
{label: 'Age', key: 'ga_age'},
{label: 'Name', key: 'rtc_name'},
{label: 'id', key: 'rtc_id'},
]
const output = arr.reduce((acc, item) => {
// Destructure the array into a key and value
let [ key, value ] = item.key.split('_');
// If the key doesn't exist on the accumulator
// add an empty object
acc[key] = acc[key] || {};
// And then set the object property to true
acc[key][value] = true;
// Return the accumulator for the next iteration
return acc;
}, {});
console.log(output);
You may use reduce for your case.
const arr = [
{ label: "id", key: "wfc_id" },
{ label: "Name", key: "wfc_name" },
{ label: "Age", key: "wfc_age" },
{ label: "id", key: "ga_id" },
{ label: "Name", key: "ga_name" },
{ label: "Age", key: "ga_age" },
{ label: "Name", key: "rtc_name" },
{ label: "id", key: "rtc_id" },
];
const o = arr.reduce((a, b) => {
const [key, prop] = b.key.split("_");
a[key] ? (a[key][prop] = true) : (a[key] = { [prop]: true });
return a;
}, {});
console.log(o);

Recursively Transform my JSON data with JS

I'm trying to figure out how to transform some JSON i'm getting back from a web service so i can easily parse it into a nice type-safe object. I want to transform this format from:
[{
"name": "AwesomePeople",
"value": [
[{
"name": "TypeId",
"value": 1
}, {
"name": "People",
"value": [
[{
"name": "id",
"value": 2
}, {
"name": "name",
"value": "Danno"
}
],
[{
"name": "id",
"value": 3
}, {
"name": "name",
"value": "Julio"
}
]
]
}
],
[{
"name": "TypeId",
"value": 2
}, {
"name": "People",
"value": [
[{
"name": "id",
"value": 4
}, {
"name": "name",
"value": "Jenna"
}
],
[{
"name": "id",
"value": 5
}, {
"name": "name",
"value": "Coolio"
}
]
]
}
]
]
}
]
To the following format:
[{
"AwesomePeople": [
[{
"TypeId": 1,
}, {
"People": [
[{
"id": 2
}, {
"firstName":"Danno"
}
],
[{
"id": 3,
}, {
"firstName": "Julio"
}
]
]
}
],
[{
"TypeId": 2
}, {
"People": [
[{
"id": 4
}, {
"firstName": "Jenna"
}
],
[{
"id": 5
}, {
"firstName": "Coolio"
}
]
]
}
]
]
}
];
Two main things need to happen, these stupid "name"/"value" pairs need to be swapped at any and all levels. For example, instead of "name": "id", "value": "3", it would be simply be "id":3. The values are sometimes are arrays, so they need to processed in a similar way...the depth is variable, so i can't assume a certain number of levels deep, so i need to keep processing everything recursively.
I have started playing with the following code...you'll see an empty "newResult" array that i'm trying to build as i traverse the original JSON, taking different action whether i'm currently looking at an object, an array, or a key/property.
let count = 0;
let result = <the original array above>
let newResult = [];
result.forEach(function(resObj) {
console.log("STARTING to TRAVERSE HIGHER LEVEL OBJECTS!");
traverse(resObj);
count++;
//we're done processing high level objects, so return from this function and enjoy the newResult!
if (count===result.length)
//return from this function
console.log(newResult);
console.log("FINISHED PROCESSING HIGHER LEVEL OBJECTS, SO DONE!");
});
//Below are the functions for traversing
function traverse(x, level) {
if (isArray(x)) {
console.log("array");
traverseArray(x);
} else if ((typeof x === 'object') && (x !== null)) {
console.log("object");
traverseObject(x);
} else {
console.log("property: "+x);
//console.log(level + x);
}
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
function traverseArray(arr, level) {
//console.log(level + "<array>");
arr.forEach(function(x) {
traverse(x);
});
}
function traverseObject(obj, level) {
var keyName, keyValue;
//console.log(level + "<object>");
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (key==="name"){
keyName = obj[key];
}else if (key==="value"){
keyValue = obj[key];
}
if (keyName && keyValue){
var newObj = {[keyName]: keyValue}
newResult.push(newObj);
//console.log("the KEY NAME IS: "+ keyName + ", and the VALUE is: "+keyValue);
}
//if we have a key value, but the value is an array, stop and
// if (isArray(newOj)
console.log("traversing..." +obj[key]);
traverse(obj[key]);
}//end if property
}//end foreach key in object
}//end traverseObject
thanks all...kudos to the person who can get their brain around this :)
You can do this with JSON.stringify and JSON.parse - with a reviver, check if the value has a name property, and if it does, return { [value.name]: value.value }:
const arr=[{name:"AwesomePeople",value:[[{name:"TypeId",value:1},{name:"People",value:[[{name:"id",value:2},{name:"name",value:"Danno"}],[{name:"id",value:3},{name:"name",value:"Julio"}]]}],[{name:"TypeId",value:2},{name:"People",value:[[{name:"id",value:4},{name:"name",value:"Jenna"}],[{name:"id",value:5},{name:"name",value:"Coolio"}]]}]]}];
const result = JSON.parse(JSON.stringify(arr, (key, value) => (
value?.name
? { [value.name]: value.value }
: value
)));
console.log(result);
If you also want to change the name values to firstName keys, add a conditional in the computed property:
const arr=[{name:"AwesomePeople",value:[[{name:"TypeId",value:1},{name:"People",value:[[{name:"id",value:2},{name:"name",value:"Danno"}],[{name:"id",value:3},{name:"name",value:"Julio"}]]}],[{name:"TypeId",value:2},{name:"People",value:[[{name:"id",value:4},{name:"name",value:"Jenna"}],[{name:"id",value:5},{name:"name",value:"Coolio"}]]}]]}];
const result = JSON.parse(JSON.stringify(arr, (key, value) => (
value?.name
? { [value.name === 'name' ? 'firstName' : value.name]: value.value }
: value
)));
console.log(result);
Manually:
const arr=[{name:"AwesomePeople",value:[[{name:"TypeId",value:1},{name:"People",value:[[{name:"id",value:2},{name:"name",value:"Danno"}],[{name:"id",value:3},{name:"name",value:"Julio"}]]}],[{name:"TypeId",value:2},{name:"People",value:[[{name:"id",value:4},{name:"name",value:"Jenna"}],[{name:"id",value:5},{name:"name",value:"Coolio"}]]}]]}];
const recurse = (val) => {
if (!val || typeof val !== 'object') return val;
if (Array.isArray(val)) return val.map(recurse);
return { [val.name === 'name' ? 'firstName' : val.name]: val.value };
};
const result = recurse(arr);
console.log(result);
I grabbed your data, quickly wrote a function to convert it, came to post it and realized that my output wasn't at all what you requested. I would just throw it away, except that it seems to me this output is much more useful than what you requested. So if you can use something like this:
{
AwesomePeople: [
{
TypeId: 1,
People: [
{id: 2, name: "Danno"},
{id: 3, name: "Julio"}
]
},
{
TypeId: 2,
People: [
{id: 4, name: "Jenna"},
{id: 5, name: "Coolio"}
]
}
]
}
then this function may help:
const convert = (xs) =>
Object .fromEntries (
xs .map (({name, value}) => [
name,
Array .isArray (value) ? value .map (convert) : value
])
)
const data = [{name: "AwesomePeople", value: [[{name: "TypeId", value: 1}, {name: "People", value: [[{name: "id", value: 2}, {name: "name", value: "Danno"}], [{name: "id", value: 3}, {name: "name", value: "Julio"}]]}], [{name: "TypeId", value: 2}, {name: "People", value: [[{name: "id", value: 4}, {name: "name", value: "Jenna"}], [{name: "id", value: 5}, {name: "name", value: "Coolio"}]]}]]}]
console .log (convert (data))
.as-console-wrapper {min-height: 100% !important; top: 0}
If not, well then maybe someone else might get some use out of it.
Here is an answer using object-scan. This code modifies the original input, which can be significantly faster than rebuilding the structure.
Note that in your input the data is a bit inconsistent: Where does firstName come from? So I've assumed consistency
// const objectScan = require('object-scan');
const data = [{ name: 'AwesomePeople', value: [ [{ name: 'TypeId', value: 1 }, { name: 'People', value: [ [{ name: 'id', value: 2 }, { name: 'name', value: 'Danno' } ], [{ name: 'id', value: 3 }, { name: 'name', value: 'Julio' } ] ] } ], [{ name: 'TypeId', value: 2 }, { name: 'People', value: [ [{ name: 'id', value: 4 }, { name: 'name', value: 'Jenna' } ], [{ name: 'id', value: 5 }, { name: 'name', value: 'Coolio' } ] ] } ] ] } ];
objectScan(['**[*].name'], {
filterFn: ({ parent }) => {
const { name, value } = parent;
delete parent.name;
delete parent.value;
parent[name] = value;
}
})(data);
console.log(data);
// => [ { AwesomePeople: [ [ { TypeId: 1 }, { People: [ [ { id: 2 }, { name: 'Danno' } ], [ { id: 3 }, { name: 'Julio' } ] ] } ], [ { TypeId: 2 }, { People: [ [ { id: 4 }, { name: 'Jenna' } ], [ { id: 5 }, { name: 'Coolio' } ] ] } ] ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.0.0"></script>
Disclaimer: I'm the author of object-scan

Filter array of objects from nested array and nested array of objects

I have the following array of object
const skus = [
{
id: 1,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material1" },
{ label: "Type", value: "Type1" }
]
},
{
id: 2,
features: ["Cotton"],
fields: [
{ label: "Material", value: "Material2" },
{ label: "Type", value: "Type2" }
]
},
{
id: 3,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material3" },
{ label: "Type", value: "Type1" }
]
}
]
And i want the expected output to be
const output = [
{ label: "features", value: ["Slim", "Cotton"] },
{ label: "Material", value: ["Material1", "Material2", "Material3"] },
{ label: "Type", value: ["Type1", "Type2"] }
]
I tried the following way
const output = [];
let featureArr = [];
let fieldsArr = []
skus.forEach(e => {
e.features.forEach(f => {
featureArr.push(f);
});
e.fields.forEach(f => {
fieldsArr.push({ label: f.label, value: f.value });
});
});
featureArr = _.uniq(featureArr);
fieldsArr = _.uniqBy(fieldsArr, 'value')
fieldsArr = _.groupBy(fieldsArr, 'label');
output.push({ label: 'Features', value: featureArr })
for (const k in fieldsArr) {
let valArr = []
valArr = fieldsArr[k].map(v => v.value)
output.push({ label: k, value: valArr });
}
I'm getting the expected output, but here multiple loops are present. Is there a way on how can i write the solution in more optimized way.
You could take a grouping function for nested properties, where a map, an array for iterating, group and value keys are handed over. The result is a map with all collected values for each group.
Later get all unique values from the map and build a new array of objects.
const
skus = [{ id: 1, features: ["Slim"], fields: [{ label: "Material", value: "Material1" }, { label: "Type", value: "Type1" }] }, { id: 2, features: ["Cotton"], fields: [{ label: "Material", value: "Material2" }, { label: "Type", value: "Type2" }] }, { id: 3, features: ["Slim"], fields: [{ label: "Material", value: "Material3" }, { label: "Type", value: "Type1" }] }],
getGrouped = (map, array, key, value) => array.reduce((m, o) =>
m.set(o[key], [...(m.get(o[key]) || []), o[value]]), map),
result = Array.from(
skus.reduce((m, o) =>
getGrouped(
m.set('features', [...(m.get('features') || []), ...o.features]),
o.fields,
'label',
'value'
),
new Map
),
([label, value]) => ({ label, value: [...new Set(value)] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
First Build an object with values as Sets. Then convert the object of sets into array of array.
const skus = [
{
id: 1,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material1" },
{ label: "Type", value: "Type1" }
]
},
{
id: 2,
features: ["Cotton"],
fields: [
{ label: "Material", value: "Material2" },
{ label: "Type", value: "Type2" }
]
},
{
id: 3,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material3" },
{ label: "Type", value: "Type1" }
]
}
];
const update = data => {
const res = {};
data.forEach(item => {
const features = res["features"] || new Set();
item.features.forEach(fea => features.add(fea));
res["features"] = features;
item.fields.forEach(field => {
const labels = res[field.label] || new Set();
labels.add(field.value);
res[field.label] = labels;
});
});
return Object.keys(res).map(key => ({ label: key, value: [...res[key]] }));
};
console.log(update(skus));
If you can use them, Sets will be your friend here:
//data
const skus = [{id: 1,features: ["Slim"],fields: [{ label: "Material", value: "Material1" },{ label: "Type", value: "Type1" }]},{id: 2,features: ["Cotton"],fields: [{ label: "Material", value: "Material2" },{ label: "Type", value: "Type2" }]},{id: 3,features: ["Slim"],fields: [{ label: "Material", value: "Material3" },{ label: "Type", value: "Type1" }]}];
//solution
const output = Object.entries(skus.reduce((map,sku) => {
sku.features.forEach(feat => map.features.add(feat));
sku.fields.forEach(field => (map[field.label] = (map[field.label] || new Set()).add(field.value)));
return map;
}, {features: new Set()})).map(([label, set]) => ({label, value: Array.from(set)}));
//display
console.log(output);
Each feature array and field array only get iterated exactly once using this approach.
If you can't use Sets, you can emulate their behavior using js objects. The goal is to use some structure that doesn't need to be iterated again to find unique values.
The following function will do the job
const fn = (array) => {
return array.reduce((result, element) => {
const features = result[0].value
const feature = element.features[0]
if (!features.includes(feature)) {
features.push(feature)
}
const materials = result[1].value
const material = element.fields[0].value
if (!materials.includes(material)) {
materials.push(material)
}
const types = result[2].value
const type = element.fields[1].value
if (!types.includes(type)) {
types.push(type)
}
return result
}, [
{ label: 'features', value: [] },
{ label: 'Material', value: [] },
{ label: 'Type', value: [] }
])
}
BUT, your object structure is quite messy, you should likely build accessor functions that extract information from your initial elements, and use some helper functions to populate your result object.
Anyway, read more about the 'reduce' function used here ;)
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce

Merge Array of Objects by Property using Lodash

I have two arrays of objects that represent email addresses that have a label and a value:
var original = [
{
label: 'private',
value: 'private#johndoe.com'
},
{
label: 'work',
value: 'work#johndoe.com'
}
];
var update = [
{
label: 'private',
value: 'me#johndoe.com'
},
{
label: 'school',
value: 'schhol#johndoe.com'
}
];
Now I want to compare and merge the two arrays by the label field, so that the result would look like this:
var result = [
{
label: 'private',
value: 'me#johndoe.com'
},
{
label: 'work',
value: 'work#johndoe.com'
},
{
label: 'school',
value: 'schol#johndoe.com'
}
]
How can I do this e.g. using lodash?
_.unionBy():
This method is like _.union except that it accepts iteratee which is invoked for each element of each arrays to generate the criterion by which uniqueness is computed. Result values are chosen from the first array in which the value occurs.
var original = [
{ label: 'private', value: 'private#johndoe.com' },
{ label: 'work', value: 'work#johndoe.com' }
];
var update = [
{ label: 'private', value: 'me#johndoe.com' },
{ label: 'school', value: 'schol#johndoe.com' }
];
var result = _.unionBy(update, original, "label");
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
Convert the lists to objects keyed by label, merge them by _.assign, and convert it back to an array. It will even retain order of the items on most browsers.
var original = [
{
label: 'private',
value: 'private#johndoe.com'
},
{
label: 'work',
value: 'work#johndoe.com'
}
];
var update = [
{
label: 'private',
value: 'me#johndoe.com'
},
{
label: 'school',
value: 'schol#johndoe.com'
}
];
console.log(
_.map(
_.assign(
_.mapKeys(original, v => v.label),
_.mapKeys(update, v => v.label)
)
)
);
// or remove more duplicated code using spread
console.log(
_.map(
_.assign(
...[original, update].map(
coll => _.mapKeys(coll, v => v.label)
)
)
)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.js"></script>
Perhaps a bit late, but all the solutions I have seen don't join both arrays correctly, they use one of the arrays to loop on and any excess elements in the second array don't get added (assuming this is what is required).
The right way is to sort both arrays and move forward within both arrays, merging the matches elements and adding the missing elements from both arrays.
Please find full solution below. This also takes O(n+m) which is the best you can get (without the computational costs for sort itself). In my code I already got the data sorted from the database.
function mergeObjectsBasedOnKey(array1, array2, compareFn, mergeFn, alreadySorted) {
var array1Index = 0;
var array2Index = 0;
const merged = [];
if (!alreadySorted) {
array1.sort(compareFn);
array2.sort(compareFn);
}
while (array1Index < array1.length && array2Index < array2.length) {
var comparedValue = compareFn(array1[array1Index], array2[array2Index]);
if (comparedValue === 0) {
merged.push(mergeFn(array1[array1Index], array2[array2Index]));
array1Index++;
array2Index++;
} else if (comparedValue < 0) {
merged.push(mergeFn(array1[array1Index]));
array1Index++;
} else {
merged.push(mergeFn(array2[array2Index]));
array2Index++;
}
}
while (array1Index < array1.length) {
merged.push(mergeFn(array1[array1Index]));
array1Index++;
}
while (array2Index < array2.length) {
merged.push(mergeFn(array2[array2Index]));
array2Index++;
}
return merged;
}
const array1 = [{
"id": 10,
isArray1: true
},
{
"id": 11,
isArray1: true
},
{
"id": 12,
isArray1: true
},
];
const array2 = [{
"id": 8,
isArray2: true
},
{
"id": 11,
isArray2: true
},
{
"id": 15,
isArray2: true
},
];
const result = mergeObjectsBasedOnKey(array1, array2, function(a, b) {
return a.id - b.id;
}, function(a, b) {
if (b) {
return _.merge(a, b);
}
return _.merge(a, {
isArray1: true,
isArray2: true
});
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
And the results would be:
[ { id: 8, isArray2: true, isArray1: true },
{ id: 10, isArray1: true, isArray2: true },
{ id: 11, isArray1: true, isArray2: true },
{ id: 12, isArray1: true, isArray2: true },
{ id: 15, isArray2: true, isArray1: true } ]
In case you are using lodash 3.x where _.unionBy() was not there, you can combine _.union() and _.uniq() to get the same result.
var original = [
{ label: 'private', value: 'private#johndoe.com' },
{ label: 'work', value: 'work#johndoe.com' }
];
var update = [
{ label: 'private', value: 'me#johndoe.com' },
{ label: 'school', value: 'schol#johndoe.com' }
];
var result = _.uniq(_.union(update, original), "label");
console.log(result);
I know it is not what asked for but just in case someone stumbled up on this page here is how you do this in ramda:
var original = [
{ label: 'private', value: 'private#johndoe.com' },
{ label: 'work', value: 'work#johndoe.com' }
];
var updated = [
{ label: 'private', value: 'me#johndoe.com' },
{ label: 'school', value: 'schol#johndoe.com' }
];
unionWith(eqBy(prop('label')), updated, original);
Here is another way to merge two objects using Lodash:
let a = [{
content: 'aaa',
name: 'bbb2'
},
{
content: 'aad',
name: 'ccd'
}
];
let b = [{
content: 'aaa',
name: 'bbb'
},
{
content: 'aad1',
name: 'ccd1'
}
];
let c = [...a, ...b];
let d = _.uniq(c, function(data) {
return data.content;
})
console.log(d);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
Perhaps a bit late, but all the solutions I have seen don't join both arrays correctly, they use one of the arrays to loop on and any excess elements in the second array don't get added (assuming this is what is required).
I had the same observation so put something together myself. This is working for my use case, which is to merge each object if the value of the 'label' field matches:
const dataSetHashes = dataSets.map(dataSet => _.keyBy(dataSet, 'label'))
const resultHash = _.merge(
{},
...dataSetLookups
)
const result = Object.values(resultLookup)

Categories