Adding multiple objects using object spread operator (ES6, JavaScript) - javascript

i have 3 objects having same data but inside array having separate service and offer id so i tried to get expected result as below mentioned and please check my try here . Thanks in advance
Object 1:
const obj1 = {
bid : 1,
mobile : 9533703390,
services : [
{
service_id : 5,
offer_id : 10,
count : 1
}
]
}
object2 :
const obj2 = {
bid : 1,
mobile : 9524703390,
services : [
{
service_id : 8,
offer_id : 12,
count : 1
}
]
}
object 3:
const obj3 = {
bid : 1,
mobile : 9524703390,
services : [
{
service_id : 5,
offer_id : 10,
count : 1
}
]
}
Final Result - each object having separate services and offer then if same offerid & serviceid came need to add count + 1 otherwise return data
const result = {
bid : 1,
mobile : 9524703390,
services : [
{
service_id : 5,
offer_id : 10,
count : 2
},
{
service_id : 8,
offer_id : 12,
count : 1
}
]
}

You can use array#reduce to merge all objects into single object and array#concat the services values. Then use array#reduce to merge all service object based on service_id in an object and reassign the values of this object to services.
const obj1 = { bid : 1, mobile : 9533703390, services : [ { service_id : 5, offer_id : 10, count : 1 } ] },
obj2 = { bid : 1, mobile : 9524703390, services : [ { service_id : 8, offer_id : 12, count : 1 } ] },
obj3 = { bid : 1, mobile : 9524703390, services : [ { service_id : 5, offer_id : 12, count : 1 } ] };
var combined = [obj1, obj2, obj3].reduce((r,o) => Object.assign({}, o, {services : r.services.concat(o.services)}));
combined.services = Object.values(combined.services.reduce((res, services) => {
if(res[services.service_id])
res[services.service_id].count += services.count;
else
res[services.service_id] = Object.assign({}, services);
return res;
},{}));
console.log(combined)

The spread operator doesn't do recursive appending or anything like that. You can however use it in conjunction with Object.assign like so:
const result = Object.assign(
obj1,
obj2,
obj3, {
services: [ ...obj1.services, ...obj2.services, ...obj3.services, ]
}); //Consolidate everything
Then you can consolidate the services:
const servicesObject = {};
result.services.forEach((service) => {
if (servicesObject[service.service_id] !== undefined) {
servicesObject[service.service_id].count += 1;
} else {
servicesObject[service.service_id] = service;
}
}); // Merge services
result.services = servicesObject;
If you still want the services to be an array then you can do
result.services = Object.entries(servicesObject)
.map(([key,value]) => value);
Check the updated fiddle http://jsfiddle.net/rkdejpab/15/

function merge(...objs) {
let result = objs.reduce((acc, item) => {
acc.bid = item.bid;
acc.mobile = item.mobile;
if (!acc.services) {
acc.services = []
acc.services.push(item.services[0]);
}
else {
let index = acc.services.findIndex(function (elem) {
return elem.service_id === item.services[0].service_id && elem.offer_id == item.services[0].offer_id;
});
if (!(index === -1)) {
acc.services[index].count += 1;
}
else {
acc.services.push(item.services[0]);
}
}
return acc
}, {});
return result;
}
console.log(merge(obj1, obj2, obj3));

Related

JavaScript - How to change object keys in an array of object?

I have an array of object :
let data = [
{ "date" : "17/03/2022", "count" : 2, "james#email.net" : 2 },
{
"date" : "17/05/2022",
"count" : 2,
"admin#email.com" : 1,
"secretary#email.uk" : 1
},
{ "date" : "17/07/2022", "count" : 7, "staff#email.us" : 7 },
];
I would like to remove "#" in the object key instead of the email address.
This is the expected output :
// Expected output:
data = [
{ "date" : "17/03/2022", "count" : 2, "james" : 2 },
{
"date" : "17/05/2022",
"count" : 2,
"admin" : 1,
"secretary" : 1
},
{ "date" : "17/07/2022", "count" : 7, "staff" : 7 },
];
Notes:
james is from james#email.net (1st element)
admin and secretary are from admin#email.com and secretary#email.uk, respectively (2nd element)
staff is from staff#email.us (3rd element)
and so on.
email as object keys are dynamic, meaning it can be "johndoe#email.co.uk", "mary#email.net", etc.
I have tried, but yet not successful :
for (let i = 0; i < data.length; i++) {
let keys = Object.keys(data[i]);
console.log(`key-${i+1} :`, keys); // [ 'date', 'count', 'admin#email.com', 'secretary#email.uk' ]
let emails = keys.filter(index => index.includes("#"));
console.log(`email-${i+1} :`, emails); // [ 'admin#email.com', 'secretary#email.uk' ]
let nameList = [];
for (let i = 0; i < emails.length; i++) {
let name = emails[i].split("#")[0];
nameList.push(name);
}
console.log(`name-${i+1} :`, nameList); // [ 'admin', 'secretary' ]
}
Thanks in advance.
You could create a function which splits the keys of the object keys at # and creates a new object using Object.fromEntries().
Here's a snippet:
const data = [{date:"17/03/2022",count:2,"james#email.net":2},{date:"17/05/2022",count:2,"admin#email.com":1,"secretary#email.uk":1},{date:"17/07/2022",count:7,"staff#email.us":7}];
const converter = o => Object.fromEntries(
Object.entries(o).map(([k, v]) => [k.split("#")[0], v])
)
console.log(
data.map(converter)
)
If Object.fromEntries() is not supported, you could use a simple loop through the array and then each object to create new objects like this:
const output = []
for (const o of data) {
const updated = {}
for (const key in o) {
updated[key.split("#")[0]] = o[key]
}
output.push(updated)
}
Try this as short as simple:
let data = [{
"date": "17/03/2022",
"count": 2,
"james#email.net": 2
},
{
"date": "17/05/2022",
"count": 2,
"admin#email.com": 1,
"secretary#email.uk": 1
},
{
"date": "17/07/2022",
"count": 7,
"staff#email.us": 7
},
];
const refinedData = JSON.parse(JSON.stringify(data));
refinedData.map((el, i) => {
Object.keys(el).map(e => {
if(e.includes('#')){
refinedData[i][e.slice(0, e.indexOf('#'))] = refinedData[i][e];
delete refinedData[i][e];
}
})
});
console.log(data, refinedData);
Try this improvement to prevent shallow copy and let me know your thoughts.
Can you try using this?
for (let i = 0; i < data.length; i++) {
let element = data[i];
let keys = Object.keys(element);
let emails = keys.filter(index => index.includes("#"));
for (let j = 0; j < emails.length; j++) {
let name = emails[j].split("#")[0];
let value = element[emails[j]];
Object.defineProperty(element, name, { value });
delete element[emails[j]];
}
}
console.log(data);

How to merge two objects based on id and group same key value pairs

var routePlan = [
{
"id" : 1,
"farmerName" : "Farmer1",
"farmerId" : 1
},
{
"id" : 2,
"farmerName" : "Farmer2",
"farmerId" : 2
},
{
"id" : 1,
"farmerName" : "Farmer3",
"farmerId" : 3
}
];
I want to merge objects having same id and create a new combined object using javascript or angularjs
var routePlan = [
{
"id" : 1,
"farmers" : [
{
"farmerName" : "Farmer1",
"farmerId" : 1
},
{
"farmerName" : "Farmer3",
"farmerId" : 3
}
]
},
{
"id" : 2,
"farmerName" : "Farmer3",
"farmerId" : 2
}
];
Please help me, I have seen lot of examples on the net but nothing seems to match my requirement
It can surely be improved, but this is a working solution:
let temp = routePlan.reduce(function(acc, item){
acc[item.id] = acc[item.id] || [];
acc[item.id].push({ "farmerName": item.farmerName, "farmerId" : item.farmerId });
return acc;
}, {});
let newRoute = Object.keys(temp).map(function(key){
let newObj = {}
if (temp[key].length > 1){
newObj.id = parseInt(key);
newObj.farmers = temp[key];
}
else
{
newObj = Object.assign({}, routePlan.find(elem => elem.id == key));
}
return newObj;
});
Note the reduce function used to group your objects by id.
You could take a hash table as reference to the groups.
var data = [{ id: 1, farmerName: "Farmer1", farmerId: 1 }, { id: 2, farmerName: "Farmer2", farmerId: 2 }, { id: 1, farmerName: "Farmer3", farmerId: 3 }],
hash = Object.create(null),
grouped = data.reduce(function (r, a) {
function getP(o) {
return ['farmerName', 'farmerId'].reduce(function (r, k) {
r[k] = o[k];
return r;
}, {});
}
if (!(a.id in hash)) {
hash[a.id] = r.push(a) - 1;
return r;
}
if (!r[hash[a.id]].farmers) {
r[hash[a.id]] = { id: a.id, farmers: [getP(r[hash[a.id]])] };
}
r[hash[a.id]].farmers.push(getP(a));
return r
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

group by unique values of property

I got array of objects (tasks). Each task has property named 'category' and 'duration'.
var tasks = [
{
_id : "123",
category : "someCategory",
duration: "3432"
},
{
_id : "113",
category : "someCategory",
duration: "23"
},
{
_id : "124",
category : "someCategory 2",
duration: "1343"
},
{
_id : "2124",
category : "someCategory 2",
duration: "1343"
},
{
_id : "7124",
category : "someCategory 5",
duration: "53"
},
{
_id : "34",
category : "someCategory",
duration: "753"
}
]
I'd like to group tasks by category (unique) and sum duration of each category.
Result should be like:
var categories = ["someCategory", "someCategory 2" ... ]
var duration = [ <summary duration of "someCategory">, <summary duration of "someCategory 2">, ... ]
I have groupBy function which gives me all categories. I can find uniqueCategories using Array.prototype.filter but still I have to sum 'duration'.
var categoryMap = groupBy(tasks, 'category');
var uniqueCategories = categoryMap.get('category').filter((x, i, a) => a.indexOf(x) == i);
function groupBy(list, property) {
var map = new Map();
list.forEach(function(item) {
const key = property;
if(!map.has(key)) {
map.set(key, [item[key]])
} else {
map.get(key).push(item[key])
}
})
return map;
}
Then I create array of { key : value } and sum by key i.e.
[
{
someCategory : 3432
},
{
someCategory : 23
}
.
.
.
]
Finally I achieve my goal but code looks messy and isn't readable at all...
Is there better approach to do it in Javascript?
You could just return one object with category: duration.
var tasks = [{"_id":"123","category":"someCategory","duration":"3432"},{"_id":"113","category":"someCategory","duration":"23"},{"_id":"124","category":"someCategory 2","duration":"1343"},{"_id":"2124","category":"someCategory 2","duration":"1343"},{"_id":"7124","category":"someCategory 5","duration":"53"},{"_id":"34","category":"someCategory","duration":"753"}]
var result = tasks.reduce(function(r, e) {
r[e.category] = (r[e.category] || 0) + +e.duration
return r;
}, {})
console.log(result)
var tasks = [{"_id":"123","category":"someCategory","duration":"3432"},{"_id":"113","category":"someCategory","duration":"23"},{"_id":"124","category":"someCategory 2","duration":"1343"},{"_id":"2124","category":"someCategory 2","duration":"1343"},{"_id":"7124","category":"someCategory 5","duration":"53"},{"_id":"34","category":"someCategory","duration":"753"}]
var arr = [];
tasks.forEach(v => arr.push(v.category));
var newArr = [...new Set(arr)];
var arr2 = [];
newArr.forEach(function(v) {
var obj = {};
obj.category = v;
obj.duration = 0;
arr2.push(obj);
});
arr2.forEach(v => tasks.forEach(c => c.category == v.category ? v.duration += parseInt(c.duration) : v));
console.log(arr2);

Javascript multiple condition array filter

I need help putting together an array search that is based on multiple conditions. Furthermore, all the conditions are conditional, meaning I may or may not need to filter on those conditions. What I have:
Array of objects to filter:
var data = [{
"_id" : ObjectId("583f6e6d14c8042dd7c979e6"),
"transid" : 1,
"acct" : "acct1",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category1",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 2,
"acct" : "acct2",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category2",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 3,
"acct" : "acct2",
"transdate" : ISODate("2016-07-31T05:00:00.000Z"),
"category" : "category1",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 4,
"acct" : "acct2",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category2",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 5,
"acct" : "acct2",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category3",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c152g2"),
"transid" : 6,
"acct" : "acct3",
"transdate" : ISODate("2016-10-31T05:00:00.000Z"),
"category" : "category3",
"amount" : 103
}]
I am filtering the above array of objects based on another array of mixed elements. The elements represent the following search fields:
"searchstring": to search on all fields in the data array for any
matched text sequence
object with key values reprsenting account type and a true or false
for value indicating if it should be used to filter
startdate to filter transdate on
enddate to filter transdate
category name to filter category on
The array that has the search conditions looks like this (but if some of the fields are not necessary they will be set to undefined or just an empty string or array):
var filtercondition = {
"p",
{acct1:true,acct2:false,acct3:true...}
"2016-06-01",
"2016-11-30",
"category3"
}
What is the best way to accomplish this? What I've devised is a separate search for each element in the filter array, but this seems non optimal and very tedious. I'm open to a redesign of my setup...
// You wrote that it's an array, so changed the braces
var filtercondition = ["p",
{acct1:true,acct2:false,acct3:true...}
"2016-06-01",
"2016-11-30",
"category3"
];
var filtered = data.filter(o => {
if(filtercondition[0] && !o.category.includes(filtercondition[o])) { // checking just the category, but you can check if any of more fields contains the conditions
return false;
}
if(filtercondition[1]) {
for(var key in filtercondition[1]) {
if(filtercondition[1][key] === true && o.acct != key) {
return false;
}
}
}
if(filtercondition[2] && o.transdate < filtercondition[2]) {
return false;
}
if(filtercondition[3] && o.transdate > filtercondition[3]) {
return false;
}
if(filtercondition[4] && o.category !== filtercondition[4]) {
return false;
}
return true;
});
Two notes:
- changed the braces of filtercondition so that it is an array, however I would suggest to use an object instead.
- this {acct1:true,acct2:false,acct3:true...} sample doesn't make sense for me, since it suggests that the acct field should be acct1 and acct3 at the same time.
Create an array of functions, each function representing a condition.
Here's some sample code which demonstrates the approach...
var conditions = [];
// Dynamically build the list of conditions
if(startDateFilter) {
conditions.push(function(item) {
return item.transdate >= startDateFilter.startDate;
});
};
if(categoryFilter) {
conditions.push(function(item) {
return item.cateogry === categoryFilter.category;
});
};
// etc etc
Once you have an array of conditions, you can use Array.prototype.every to run each condition on an item.
var itemsMatchingCondition = data.filter(function(d) {
return conditions.every(function(c) {
return c(d);
});
});
Or, using the more compact arrow functions:
const itemsMatchingCondition = data.filter(d => conditions.every(c => c(d));
First, you'll want to use brackets for your array not curly braces:
var filtercondition = [
"p",
{acct1:true,acct2:false,acct3:true...},
"2016-06-01",
"2016-11-30",
"category3"
];
Then again, I don't think that an array is the best data type for that. Try an object like this:
var filtercondition = {
query: "p",
accounts: {acct1:true,acct2:false,acct3:true...},
date1: "2016-06-01",
date2: "2016-11-30",
category: "category3"
};
Then, try using Array.prototype.filter:
var filtered = data.filter(function(obj) {
for (var key in filtercondition) {
// if condition not met return false
}
return true;
});
I'd go with a bunch of small granular functions and compose them.
//only some utilities, from the top of my mind
var identity = v => v;
//string-related
var string = v => v == null? "": String(v);
var startsWith = needle => haystack => string(haystack).startsWith(needle);
var endsWith = needle => haystack => string(haystack).endsWith(needle);
var contains = needle => haystack => string(haystack).contains(needle);
//do sth with an object
var prop = key => obj => obj != null && prop in obj? obj[prop]: undefined;
var someProp = fn => obj => obj != null && Object.keys(obj).some(k => fn(k) );
var someValue = fn => obj => obj != null && Object.keys(obj).some(k => fn(obj[k]) );
//logic
var eq = b => a => a === b;
var not = fn => function(){ return !fn.apply(this, arguments) };
var and = (...funcs) => funcs.reduce((a, b) => function(){
return a.apply(this, arguments) && b.apply(this, arguments);
});
var or = (...funcs) => funcs.reduce((a, b) => function(){
return a.apply(this, arguments) || b.apply(this, arguments);
});
//composition
var compose = (...funcs) => funcs.reduce((a, b) => v => return a(b(v)));
var chain = (...funcs) => funcs.reduceRight((a, b) => v => return a(b(v)));
//and whatever else you want/need
//but stay granular, don't put too much logic into a single function
and an example composition:
var filterFn = and(
//some value contains "p"
someValue(contains("p")),
//and
chain(
//property "foo"
prop("foo"),
or(
//either contains "asdf"
contains("asdf"),
//or startsWith "123"
startsWith("123")
)
),
)
since I don't know how you build your filterconditions, I cannot tell you exactly how to parse them into such a composition, but you could compose them like this:
//start with something basic, so we don't ever have to check wether filterFn is null
var filterFn = identity;
//and extend/compose it depending on some conditions
if(/*hasQuery*/){
filterFn = and(
// previous filterFn(obj) && some value on obj contains `query`
filterFn,
someValue(contains(query)))
)
}
if(/*condition*/){
//extend filterFn
filterFn = or(
// (obj.foo === null) || previous filterFn(obj)
chain(prop("foo"), eq(null)),
filterFn
);
}
and so on
First, some points:
Your data object is invalid if you're going to use it in the browser. Probably the data comes from MongoDB, right? Your backend (data source) should have a method to encode it properly and remove ObjectID and ISODate references.
Your filtercondition is not a valid JavaScript object/JSON. Check my example.
So, you can filter your data array with Array#filter method.
Something like that:
let data = [{
"_id" : "583f6e6d14c8042dd7c979e6",
"transid" : 1,
"acct" : "acct1",
"transdate" : "2012-01-31T05:00:00.000Z",
"category" : "category1",
"amount" : 103
},
{
"_id" : "583f6e6d14c8042dd7c2132t6",
"transid" : 2,
"acct" : "acct2",
"transdate" : "2012-01-31T05:00:00.000Z",
"category" : "category2",
"amount" : 103
},
{
"_id" : "583f6e6d14c8042dd7c2132t6",
"transid" : 5,
"acct" : "acct2",
"transdate" : "2012-01-31T05:00:00.000Z",
"category" : "category3",
"amount" : 103
}];
let filterToApply = {
acct: {
acct1: true,
acct2: false,
acct3: true
},
initialDate: "2016-06-01",
finalDate: "2016-11-30",
category: "category3"
}
let filterData = (array, filter) => {
return array.filter( (item) => {
/* here, you iterate each item and compare with your filter,
if the item pass, you must return true. Otherwise, false */
/* e.g.: category check (if present only) */
if (filter.category && filter.category !== item.category)
return false;
}
/* add other criterias check... */
return true;
});
}
let dataFiltered = filterData(data, filterToApply);
console.log(dataFiltered);
If you want to filter an array with multiple conditions and the conditions may be optional, then use the following method.
const data = [
{ name: 'John', age: 25, city: 'New York' },
{ name: 'John', age: 25, city: 'New' },
{ name: 'Jane', age: 32, city: 'Los Angeles' },
{ name: 'Bob', age: 45, city: 'New York' },
{ name: 'Alice', age: 38, city: 'Los Angeles' }
];
const filteredData = (n, c, a) => data.filter(item => {
if (n || c || a) {
return (n ? item.name === n : true) && (c ? item.city === c : true) && (a ? item.age === a : true); // keep adding conditons as much as u want
}
});
console.log(filteredData('John', null, 25));
console.log(filteredData(null, 'Los Angeles', 38));
console.log(filteredData(null, 'Los Angeles', null));
You can chain as many as conditions

How to create nested arrays based on arrays of strings in javascript?

I am trying to create nested arrays with array of strings.
Each string object on the array is delimited by a '|' and that char its uses to create a nested array over an already existing array.
edit fix IE: current array
var arr = [
{ val : 'root|leaf|lead2|boo|foo|lee'},
{ val : 'root|leaf|lead3|boo|foo|lee'},
{ val : 'root|leaf2|boo'},
{ val : 'root|leaf2|foo'},
{ val : 'root|leaf2|leaf3|more'},
{ val : 'root|leaf2|leaf3|things'},
{ val : 'root|leaf2|leaf3|here'},
{ val : 'sibling|leaf|leaf2|boo'},
{ val : 'sibling|leaf|leaf2|foo'},
{ val : 'sibling|leaf|leaf2|lee'},
{ val : 'sibling|boo'},
{ val : 'sibling|foo'},
{ val : 'sibling|boo|leaf3'},
{ val : 'sibling|boo|leaf3|more'},
{ val : 'sibling|boo|leaf3|things'},
{ val : 'sibling|boo|leaf3|here'},
{ val : 'sibling|ops'},
];
var nested = [
root = [
leaf = [
leaf2 = [
'boo', 'foo', 'lee'
],
leaf3 = [
'boo', 'foo', 'lee'
]
],
leaf2 = [
'boo', 'foo', leaf3 = [
'more', 'things', 'here'
]
]
],
sibling = [
leaf = [
leaf = [
leaf2 = [
'boo', 'foo', 'lee'
]
]
],
'ops',
'boo', 'foo', leaf3 = [
'more', 'things', 'here'
]
]
];
You can find here a functional approach, by using .map() and .reduce() methods. The idea is to parse the path by splitting over the | character, and then build the object on the fly.
const arr = [
{cat : 'one|two|thre|boo'},
{cat : 'one|two|boo|boo|ouch'},
{cat : 'one|two|thre|boo|lee'},
{cat : 'one|hey|something|other'},
{cat : 'one|hey|keys|other'},
{cat : 'this|blaj|something|other'},
];
function create(array) {
const parse = elm => elm.cat.split('|');
const build = (keys, obj, acc) => {
keys.reduce((a, b) => {
if (!a[b]) a[b] = {};
return a[b];
}, obj);
Object.assign(acc, obj);
return acc;
};
const obj = {};
return array
.map(a => parse(a))
.reduce((acc, keys) => build(keys, obj, {}), {});
}
console.log(create(arr))
You can find the Working plunkr

Categories

Resources