Merge values of objects with same keys into an array using lodash - javascript

I have an array of objects which looks like this:
const childrens = [
{ orderName: "#1004" },
{ orderName: "#1006" },
{ orderName: "#1007" },
{ orderName: "#1005" },
{ deliveryDate: "25-25-25" },
{ signature: "xq" },
];
I want this to be converted into object and have values of same keys as an array of values like this
{
orderName: [ '#1004', '#1006', '#1007', '#1005' ],
deliveryDate: [ '25-25-25' ],
signature: [ 'xq' ]
}
The way i'm doing this right now is by using a reduce function like this
_.reduce(
childrens,
(acc, cur) => {
const pairs = _.chain(cur).toPairs().flatten().value();
if (acc[pairs[0]] === undefined) {
acc[pairs[0]] = [pairs[1]];
} else {
acc[pairs[0]].push(pairs[1]);
}
return acc;
},
{},
);
I'm wondering if there's a cleaner way using built-in lodash functions?

You can use _.mergeWith() with array spread. When merging 2 values, check if the 1st (a) is still undefined (the empty object), if so return the 2nd item wrapped in an array, if it exists, use array spread to concat them:
const children = [{"orderName":"#1004"},{"orderName":"#1006"},{"orderName":"#1007"},{"orderName":"#1005"},{"deliveryDate":"25-25-25"},{"signature":"xq"}];
const result = _.mergeWith({}, ...children, (a, b) =>
_.isUndefined(a) ? [b] : [...a, b]
);
console.log(result);
<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>

If you don't use lodash, you can do like below.
const children = [
{ orderName: "#1004" },
{ orderName: "#1006" },
{ orderName: "#1007" },
{ orderName: "#1005" },
{ deliveryDate: "25-25-25" },
{ signature: "xq" },
];
const output = children.reduce((a, b) => {
const key = Object.keys(b)[0];
if (!a[key]) {
a[key] = [b[key]];
} else {
a[key].push(b[key]);
}
return a;
}, {});
console.log(output);

Something like this will work on modern JS platforms (added bonus, you don't need lodash to use reduce). Essentially:
loop over each item in childrens
get the key(s) for that object
loop over the key(s)
if result doesn't have that key set it to an empty array
push the value for the key to the relevant array on the result
const desiredOutput = childrens.reduce(
(acc, current) => {
const keys = Object.keys(current);
keys.forEach(key => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(current[key]);
});
return acc;
},
{}
);

You can simply get the result using vanilla JS using reduce and Object.entries
(acc[prop] = acc[prop] ?? []).push(value);
const childrens = [
{ orderName: "#1004" },
{ orderName: "#1006" },
{ orderName: "#1007" },
{ orderName: "#1005" },
{ deliveryDate: "25-25-25" },
{ signature: "xq" },
];
const result = childrens.reduce((acc, curr) => {
const [[prop, value]] = Object.entries(curr);
(acc[prop] = acc[prop] ?? []).push(value);
return acc;
}, {});
console.log(result);

Related

Create an array of object properties for each item that has the same key

I'm merging two objects together to create a filter object. However I want to group the merged objects property values where the keys are the same.
So...
[{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}]
becomes
[{category: ['furniture', 'mirrors']}, {availability: 'in_stock'}]
any ideas?
With lodash you merge the entire array to a new object by spreading into _.mergeWith(). The customizer should use empty arrays as default values for the current values, and concat the values. Use _.map() to convert back to an array.
const data = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const result = _.map(
_.mergeWith({}, ...data, (a = [], b = [], key) => a.concat(b)),
(val, key) => ({ [key]: val })
)
console.log(result)
<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>
Using vanilla JS, reduce the array to a Map using the objects' keys as the keys of the Map, with an empty array as the value, and push the objects' values into the arrays. Use Array.from() to convert the Map to an array.
const data = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const result = Array.from(
data.reduce((acc, obj) => {
Object.entries(obj)
.forEach(([key, val]) => {
if(!acc.has(key)) acc.set(key, [])
acc.get(key).push(val)
})
return acc
}, new Map()),
([key, val]) => ({ [key]: val })
)
console.log(result)
You can use reduce like this:
const data = [
{ category: 'furniture' },
{ category: 'mirrors' },
{ availability: 'in_stock' }
];
const result = data.reduce(
(a, x) => {
const key = Object.keys(x)[0]; // find the key of the current object
if (!a.tmp[key]) { // if the current key doesn't exist in the lookup object (tmp) yet ...
a.tmp[key] = []; // create an empty array in the lookup object for the current key
a.result.push({ [key]: a.tmp[key] }); // push the current object to the result
}
a.tmp[key].push(x[key]); // push the current value to the array
return a;
},
{ result: [], tmp: {} },
).result;
console.log(result);
I'm sure there are easier ways to achieve this, but that's the best I can come up with right now.
we can also achieve this by using forEach loop :
const input = [{category: 'furniture'}, {category: 'mirrors'}, {availability: 'in_stock'}];
const resultObj = {};
const resultArr = [];
input.forEach((obj) => {
resultObj[Object.keys(obj)[0]] = [];
})
input.forEach((obj) => {
resultObj[Object.keys(obj)[0]].push(obj[Object.keys(obj)[0]]);
resultArr.push(resultObj);
})
console.log([...new Set(resultArr)]);
Another one reduce solution
const arr = [{category: 'furniture', category2: 'furniture2'}, {category: 'mirrors'}, {availability: 'in_stock'}]
const result = Object.values(arr
.flatMap((obj) => Object.entries(obj))
.reduce((acc, [key, value]) => {
acc[key] = acc[key]
? {[key]: [...acc[key][key], value] }
: {[key]: [value] }
return acc;
}, {}));
console.log(result)
.as-console-wrapper{min-height: 100%!important; top: 0}
A generic implementation could achieve a merger of any kind of objects regardless of amount and kind of an(y) object's property names.
Since the result of such an implementation is an object, one needs additional treatment in order to cover the OP's requirement(s).
function mergeAndCollectItemEntries(result, item) {
// return the programmatically aggregated merger/result.
return Object
// get an item's entry array.
.entries(item)
// for each key-value pair ...
.reduce((merger, [key, value]) => {
// ... access and/or create a `key` specific array ...
// ... and push `value` into this array.
(merger[key] ??= []).push(value);
// return the programmatically aggregated merger/result.
return merger;
}, result);
}
const sampleData = [
{ category: 'furniture' },
{ category: 'mirrors' },
{ availability: 'in_stock' },
];
const mergedData = sampleData
.reduce(mergeAndCollectItemEntries, {});
const mergedDataList = Object
.entries(
sampleData
.reduce(mergeAndCollectItemEntries, {})
)
.map(entry => Object.fromEntries([entry]));
//.map(([key, value]) => ({ [key]: value }));
console.log({
sampleData,
mergedData,
mergedDataList,
});
console.log(
Object
.entries([
{ category: 'furniture', foo: 'baz' },
{ category: 'mirrors', bar: 'bizz' },
{ availability: 'in_stock', bar: 'buzz' },
].reduce(
mergeAndCollectItemEntries, {}
)
).map(
([key, value]) => ({ [key]: value })
//entry => Object.fromEntries([entry])
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Another approach here with building an tracking object to merge the values.
Handle the cases of single value keep as string and multiple values as array per the expected output.
const merge = (arr, output = {}) => {
arr.forEach((item) => {
const [[key, val]] = Object.entries(item);
if (key in output) {
output[key] = Array.isArray(output[key])
? output[key].concat(val)
: [output[key]].concat(val);
} else {
output[key] = val;
}
});
return Object.entries(output).map(([key, val]) => ({ [key]: val }));
};
const data = [
{ category: "furniture" },
{ category: "mirrors" },
{ availability: "in_stock" },
];
console.log(merge(data));

Javascript Object key sort using moment.js

I have the following object structure
{ "Apr-18" : { ... },
"Jan-18" : { ... },
"Feb-18" : { ... },
...
}
I am trying to sort the month (MMM-YY) keys so that it shows as follows
{ "Jan-18" : { ... },
"Feb-18" : { ... },
"Apr-18" : { ... },
...
}
My code for this is below. I am using moment.js to convert the date into its epoch for the sort comparison. I have roughly followed the solution shown here Sort JavaScript object by key However it's not working.
The console.log returns the object as it was, no sorting has occurred. What am I missing?
const object = {
"Apr-18" : { "a":"b" },
"Jan-18" : { "c":"d" },
"Feb-18" : { "e":"f" }
}
const sortObjectMonths = (obj) => Object.fromEntries(Object.entries(obj).sort( (a, b) =>
Date.parse(moment(a, "MMM-YY") - Date.parse(moment(b, "MMM-YY")))
));
let sorted = sortObjectMonths(object)
console.log(sorted)
You can use Object.entries() to get the object property keys and values, then use Array.sort() to sort them using moment. We can simply subtract the moment values to sort them.
The Array.sort() accepts two arguments, firstEl, secondEl, in this case that will be [key1, value1], [key2, value2]. We can use destructuring to write these as ([a,],[b,]), where a and b are the object keys (e.g. 'Apr-18').
Then we'll use Object.fromEntries() to get our sorted object.
const object = {
"Apr-18" : { "a":"b" },
"Jan-18" : { "c":"d" },
"Feb-18" : { "e":"f" },
}
console.log('Original object:', object)
const sortedObject = Object.fromEntries(
Object.entries(object).sort(([a,],[b,]) => {
return moment(a, "MMM-YY") - moment(b, "MMM-YY");
})
)
console.log('Sorted object:', sortedObject)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" referrerpolicy="no-referrer"></script>
Without moment
const months = ["Jan","Feb","Mar","Apr"]
const object = {
"Apr-18" : { "a":"b" },
"Jan-18" : { "c":"d" },
"Feb-18" : { "e":"f" },
}
const sortedObject = Object.fromEntries(
Object.entries(object)
.sort(([a,],[b,]) => months.indexOf(a.split("-")[0]) - months.indexOf(b.split("-")[0]))
)
console.log('Sorted object:', sortedObject)
Your code is almost okay but in .sort() the element a and b both are arrays of key and value. Key is at index 0 and value at index 1. Date.parse() won't work and converting the value by using new Date() is suggested. So, your code should be -
const moment = require("moment");
const sort = {
clientname: {
"Feb-18": { test: "c" },
"Jan-18": { test: "a" },
"Apr-18": { test: "v" },
},
};
const sortObjectMonths = (obj) => {
return Object.fromEntries(
Object.entries(obj).sort(
(a, b) => moment(new Date(a[0])) - moment(new Date(b[0]))
)
);
};
let sorted = sortObjectMonths(sort.clientname);
console.log(sorted);

Filter object of object of array properties by key javascript

I have this objects i want to filter articles by news ,funny and sports
const articles = {
article_1: {
tags: ['news', 'funny']
},
article_2: {
tags: ['sports', 'funny']
}
}
i want to filter to have result
const articlesByTag = groupArticlesByTag(articles);
articlesByTag = {
news: ['article 1'],
funny: ['article 1', 'article 2'],
sports: ['article 2']
}
You can easiy achieve this result using Object.entries, reduce and forEach.
const articles = {
article_1: {
tags: ["news", "funny"],
},
article_2: {
tags: ["sports", "funny"],
},
};
function groupArticlesByTag(obj) {
return Object.entries(obj).reduce((acc, [key, val]) => {
val.tags.forEach((tag) => {
if (acc[tag]) {
acc[tag].push(key);
} else {
acc[tag] = [key];
}
});
return acc;
}, {});
}
const articlesByTag = groupArticlesByTag(articles);
console.log(articlesByTag);
You can even make it a bit shorter with Logical nullish assignment (??=)
const articles = {
article_1: {
tags: ["news", "funny"],
},
article_2: {
tags: ["sports", "funny"],
},
};
function groupArticlesByTag(obj) {
return Object.entries(obj).reduce((acc, [key, val]) => {
val.tags.forEach((tag) => {
(acc[tag] ??= []).push(key);
});
return acc;
}, {});
}
const articlesByTag = groupArticlesByTag(articles);
console.log(articlesByTag);
Object.keys() -> gives an array of all the keys of an object.
.forEach() -> iterate through an array of items
If the key already exists then push to the array, otherwise create an array of length 1 with the key.
let groupArticlesByTag = (articles) => {
let articlesMap = {};
Object.keys(articles).map((x) => {
articles[x].tags.forEach((y)=> {
if(articlesMap[y]==undefined){
articlesMap[y] = [x]
}
else{
articlesMap[y].push(x);
}
});
});
return articlesMap;
}

Join array of object to new array object

I have data array object like this:
const data = [
{Name: "dian", Job: "programmer"},
{Name: "dian", Job: "gamer"},
{Name: "candra", Job: "programmer"},
]
My goal is to create new data where a have same value join b.
Example output:
const new_data = [
{Name: "dian", Jobs: [{Job: "programmer"}, {Job: "gamer"}]},
{Name: "candra", Jobs: [{Job: "programmer"}]},
]
I think you can use Array.prototype.reduce() to achive your goal. From the documentation:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
One possible solution:
const data = [
{Name:'dian', Job:'programer'},
{Name:'dian', Job:'gamers'},
{Name:'candra', Job:'programer'}
];
const result = data.reduce((a, current) => {
const found = a.find(f => f.Name === current.Name);
if (found) {
found.Jobs.push({Job: current.Job});
} else {
a.push({
Name: current.Name,
Jobs: [
{ Job: current.Job }
]
});
}
return a;
}, []);
console.log(result);
I hope that helps!
use reduce.
const data = [
{ Name: "dian", Job: "programer" },
{ Name: "dian", Job: "gamers" },
{ Name: "candra", Job: "programer" }
];
const output = Object.values(data.reduce((a, { Name, Job }, i) => {
if (!a[Name]) {
a[Name] = { Name, Jobs: [] };
}
a[Name].Jobs.push({ Job });
return a;
}, {}));
console.log(output);
Here's one approach:
const combineJobs = (data) =>
Object .values (data .reduce (
(a, {Name, Job}, _, __, curr = a [Name] || {Name, jobs: []}) =>
({... a, [Name]: ({... curr, jobs: [... curr .jobs, {Job}]})}),
{}
))
const data = [{Name: "dian", Job: "programmer"}, {Name: "dian", Job: "gamer"}, {Name: "candra", Job: "programmer"}]
console .log (combineJobs (data))
We simply fold our objects into a structure that looks like
{
dian: {Name: 'dian', jobs: [Job:'programer'}, {Job: 'gamer'}]},
candra: {Name: 'candra', jobs: [Job:'programer'}]},
}
then use Object .values to turn it into an appropriate array.
One advantage of this technique is that if your data actually has additional fields not displayed in the question (imagine you have age, and avatar properties as well, for instance), you can extend it easily using a rest parameter:
const combineJobs = (data) =>
Object .values (data .reduce (
(a, {Name, Job, ...rest}, _, __, curr = a [Name] || {Name, jobs: [], ...rest}) =>
// ^^^^^^^ ^^^^^^^
({... a, [Name]: ({... curr, jobs: [... curr .jobs, {Job}]})}),
{}
))
and all those additional parameters would be included.
function modifyArray(data) {
function getNewObject(data) {
const newObject = {
Name: data.Name
};
Object.keys(data).filter(key => key !== 'Name').forEach(key => {
newObject[key+'s'] = [];
newObject[key+'s'].push({
[key]: data[key]
});
});
return newObject;
}
function appendData(obj, data) {
Object.keys(data).filter(key => key !== 'Name').forEach(key => {
obj[key+'s'].push({
[key]: data[key]
});
});
}
const reqArray = [];
data.forEach(d => {
const objToModify = reqArray.find(a => a.Name === d.Name);
if (!objToModify) {
reqArray.push(getNewObject(d));
} else {
appendData(objToModify, d);
}
});
return reqArray;
}
let data =[
{Name:'dian', Job:'programer' },
{Name:'dian', Job:'gamers' },
{Name:'candra', Job:'programer' }
];
console.log(modifyArray(data));

Convert string to nested array object by splitting the string javascript

Convert dot notation strings to array objects,
Eg.,
let obj = { 'user-0-address-pincode': 665766, 'user-0-address-city': 'Chennai', 'user-1-address-pincode': 32432, 'user-1-address-city': 'Bangalore'};
// Expectation output will be
{
user: [
{
address: {pincode: 665766, city: 'Chennai'}
},
{
address: {pincode: 32432, city: 'Bangalore'}
}
]
}
Please help me to resolve this problem.
You can use reduce and split methods to create a function that will take your object with keys as paths and then based on those keys create nested structure.
let obj = {
'user-0-address-pincode': 665766,
'user-0-address-city': 'Chennai',
'user-1-address-pincode': 32432,
'user-1-address-city': 'Bangalore'
};
function parse(data) {
return Object.keys(obj).reduce((r, k) => {
k.split('-').reduce((a, e, i, arr) => {
const next = arr[i + 1]
if (!next) return a[e] = data[k]
else return a[e] || (a[e] = (!isNaN(+next) ? [] : {}))
}, r)
return r;
}, {})
}
const result = parse(obj)
console.log(result)

Categories