Union two objects, but omit falsy values - javascript

How can I union two objects, but omit to merge falsy values like 0, "", false, from the second object?
I want it to work in both ways and doesn't depend on the order i pass the objects
So this example is not an options: const union = {...obj2, ...obj1};
const obj1 = {a: 1, b: 2, c: { c1: 12, c2: 15 }};
const obj2 = {a: 1, b: 2, c: { c1: 0, c2: 0 }, d: 3};
const union = {...obj1, ...obj2};
console.log(union);
Desired output:
{
"a": 1,
"b": 2,
"c": {
"c1": 12,
"c2": 15
},
"d": 3
}

You could take a recursive funtion to merge nested objects with a condition.
function union(a, b) {
return [a, b].reduce((r, o) => {
Object.entries(o).forEach(([k, v]) => {
if (!v) return;
r[k] = typeof v === 'object'
? union(r[k] || {}, v)
: v;
})
return r;
}, {});
}
console.log(union(
{ a: 1, b: 2, c: { c1: 12, c2: 15 } },
{ a: 1, b: 2, c: { c1: 0, c2: 0 }, d: 3 }
));

Related

How to create an array from an array of objects by same key

I have an array of objects.
I want to merge the objects into a single array by same key. At that time, I also want to include other value in the array together.
It doesn't matter whether the merged array is an array or an object.
Current array:
[
{
"datetime": "2022-01-10",
"a": 0.5,
"b": 80.6,
"c": 1002.2
},
{
"datetime": "2022-01-11",
"a": 0.7,
"b": 80.4,
"c": 1002.4
},
{
"datetime": "2022-01-12",
"a": 0.4,
"b": 80.2,
"c": 1002.3
}
]
Expected result:
[
[
["2022-01-10", 0.5], ["2022-01-11", 0.7], ["2022-01-12", 0.4]
],
[
["2022-01-10", 80.6], ["2022-01-11", 80.4], ["2022-01-12", 1002.4]
],
[
["2022-01-10", 1002.2], ["2022-01-11", 1002.4], ["2022-01-12", 1002.3]
]
]
or
{
"a": [
["2022-01-10", 0.5], ["2022-01-11", 0.7], ["2022-01-12", 0.4]
],
"b": [
["2022-01-10", 80.6], ["2022-01-11", 80.4], ["2022-01-12", 1002.4]
],
"c": [
["2022-01-10", 1002.2], ["2022-01-11", 1002.4], ["2022-01-12", 1002.3]
]
}
I use forEach() and it works.
But I want to know if there are other ways.
const foo = [[], [], []];
json.forEach((item) => {
const [a, b, c] = foo;
a.push([item.datetime, item.a]);
b.push([item.datetime, item.b]);
c.push([item.datetime, item.c]);
});
As for generic solutions (only the datetime property of each item needs to be known, thus the solutions are agnostic to all other properties) to both of the OP's use cases,
the array of arrays where the nested array items are tuples of distinct datetime values and values of same item keys
and the object of arrays where the arrays are the same but get referred to by keys which are distinct from datetime,
one easily could use the same reduce based approach only that
for the latter result (object based key specific array of arrays) one passes an object as initial value and creates and aggregates the key specific nested arrays upon the currently processed key of each item's rest-property data-entries,
whereas for the former result (array of arrays) the initial value is an array where one needs to create and/or access each key specific inner array by the current index of each of the rest-data's values.
const sampleData = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
console.log(
sampleData
.reduce((result, { datetime, ...rest }) => {
Object
.values(rest)
.forEach((value, idx) =>
(result[idx] ??= []).push([datetime, value])
);
return result;
}, [])
);
console.log(
sampleData
.reduce((result, { datetime, ...rest }) => {
Object
.entries(rest)
.forEach(([key, value]) =>
(result[key] ??= []).push([datetime, value])
);
return result;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the key (insertion) order of the original array items can not be guarantied amongst all items, one should use the key-specific second solution, where one would pass the created object to Object.values in order to get an array of arrays ...
const sampleData = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
console.log(
Object
.values(
sampleData
.reduce((result, { datetime, ...rest }) => {
Object
.entries(rest)
.forEach(([key, value]) =>
(result[key] ??= []).push([datetime, value])
);
return result;
}, {})
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
You can use reduce function where the initial value will be an empty object.
after that, you can check if that object contains that particular key or not and push the data accordingly
a = [
{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
},
{
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
},
{
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
},
];
const solution = (key) => {
return a.reduce((acc, { [key]: keyValue, ...next }) => {
Object.entries(next).forEach(([dataKey, dataValue]) => {
(acc[dataKey] ??= []).push([keyValue, dataValue]);
});
return acc;
}, {});
};
console.log('Solution 1: ',Object.values(solution("datetime")));
console.log('Solution 2: ',solution("datetime"));
you can use javascript reduce to optimize this:
const data = temp.reduce((acc,{a,b,c,datetime})=>{
const [first,second,third] = acc;
first.push([datetime, a]);
second.push([datetime, b]);
third.push([datetime, c]);
return acc;
},[[],[],[]])
const Json = [
{
"datetime": "2022-01-10",
"a": 0.5,
"b": 80.6,
"c": 1002.2
},
{
"datetime": "2022-01-11",
"a": 0.7,
"b": 80.4,
"c": 1002.4
},
{
"datetime": "2022-01-12",
"a": 0.4,
"b": 80.2,
"c": 1002.3
}
]
const func = (arr, i) => {
let result = [];
const constants = ["a", "b", "c"];
for (let index = 0; index < arr.length; index++) {
result.push([arr[index].datetime, arr[index][`${constants[i]}`]]);
}
return result;
}
const result = Json.map((d, i) => {
return func(Json, i);
});
console.log(result)
See this it uses map and then returns an array just like your ist
const data = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
const res = data.map((m ,_) => [
[m.datetime,m.a],
[m.datetime,m.b],
[m.datetime,m.c],
]);
console.log({ res });
Or 2nd method because of comments
const data = [{
datetime: "2022-01-10",
a: 0.5,
b: 80.6,
c: 1002.2,
}, {
datetime: "2022-01-11",
a: 0.7,
b: 80.4,
c: 1002.4,
}, {
datetime: "2022-01-12",
a: 0.4,
b: 80.2,
c: 1002.3,
}];
const res = ["a","b","c"].map(key => data.map(obj => [
obj.datetime,
obj[key],
]));
console.log({ res });

How to elegantly merge multiple objects with overlapping keys?

Let's consider multiple objects with overlapping keys, where each key indicates a week of the year and the values are objects of integer test results, like
const ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } };
const cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } };
const xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } };
What is an elegant way to merge them to a single object that contains all weeks as keys and the values as merged-objects, such that:
const merged_tests = {
week1: { a: 4, b: 6, x: 1, y: 1 },
week2: { a: 0, b: 9, c: 2, d: 5 },
week3: { c: 6, d: 7 },
week4: { x: 100, y: 123 },
};
Using Array#reduce, iterate over the objects while updating the final one (accumulator)
In each iteration, using Object#entries and Array#forEach, iterate over the pairs of the current object and update the final one
const ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } };
const cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } };
const xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } };
const merged = [ab_tests, cd_tests, xy_tests].reduce((merged, current) => {
Object.entries(current).forEach(([key, value]) => {
merged[key] ??= {};
merged[key] = { ...merged[key], ...value };
});
return merged;
}, {});
console.log(merged);
You could loop through the key of each object and update an output object with the same key
const inputs = [ab_tests, cd_tests, xy_tests],
output = { }
for (const o of inputs) {
for (const key in o)
Object.assign(output[key] ??= {}, o[key])
}
Here's a snippet:
const ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } },
cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } },
xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } },
inputs = [ab_tests, cd_tests, xy_tests],
output = {}
for (const o of inputs) {
for (const key in o)
Object.assign(output[key] ??= {}, o[key])
}
console.log(output)
flatMap(Object.entries) flattens things so there's only one loop to read, making it a little more readable in my opinion.
function merge(...tests) {
return tests.flatMap(Object.entries).reduce(
(obj, [k, v]) => Object.assign(obj, {[k]: {...obj[k], ...v}}), {});
}
console.log(merge(ab_tests, cd_tests, xy_tests));
You could reduce the array of objects by grouping with the keys of the outer objects.
const
merge = array => array.reduce((r, o) => Object
.entries(o)
.reduce((t, [k, q]) => {
Object.assign(t[k] ??= {}, q);
return t;
}, r),
{}),
ab_tests = { week1: { a: 4, b: 6 }, week2: { a: 0, b: 9 } },
cd_tests = { week2: { c: 2, d: 5 }, week3: { c: 6, d: 7 } },
xy_tests = { week1: { x: 1, y: 1 }, week4: { x: 100, y: 123 } },
result = merge([ab_tests, cd_tests, xy_tests]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

return object in an array with the most props

Say I have an array of objects like this:
const arr = [
{ a: 1, b: 2, c: 3, d: 4 },
{ a: 1 },
{ a: 1, b: 2, c: 3 },
{ a: 1, b: 2 }
];
How can I return the object with the most properties/keys? Preferably using in an efficient and terse manner using higher order functions.
You could assign to a single object.
const
array = [{ a: 1, b: 2, c: 3, d: 4 }, { a: 1 }, { a: 1, b: 2, c: 3 }, { a: 1, b: 2 }],
object = Object.assign({}, ...array);
console.log(object);
If you have different values, you could reduce the array.
const
array = [{ a: 1, b: 2, c: 3, d: 4 }, { a: 1 }, { a: 1, b: 2, c: 3 }, { a: 1, b: 2 }],
object = array.reduce((a, b) => Object.keys(a).length > Object.keys(b).length
? a
: b
);
console.log(object);
You can get the number of keys from an object by calling Object.keys(obj) and then checking it's length property.
With that, you could reduce the array by checking each pair of objects and return the one with the most keys as a one liner:
const biggestObject =
arr.reduce((a, b) => Object.keys(a).length > Object.keys(b).length ? a : b);

How can I sum properties from two objects?

I have multiple JavaScript objects:
{
a: 12,
b: 8,
c: 17
}
and
{
a: 2,
b: 4,
c: 1
}
I need to sum these two object by keys
Result:
{
a: 14,
b: 12,
c: 18
}
Do you have any solutions in JavaScript?
I use Object.keys.map but it's too long because I have like 100 elements in my object.
You can use reduce for that, below function takes as many objects as you want and sums them by key:
var obj1 = {
a: 12,
b: 8,
c: 17
};
var obj2 = {
a: 12,
b: 8,
c: 17
};
var obj3 = {
a: 12,
b: 8,
c: 17
};
function sumObjectsByKey(...objs) {
return objs.reduce((a, b) => {
for (let k in b) {
if (b.hasOwnProperty(k))
a[k] = (a[k] || 0) + b[k];
}
return a;
}, {});
}
console.log(sumObjectsByKey(obj1, obj2, obj3));
A little bit deeper, all you want as long as objects are equivalent!
const arr = [{
a: 12,
b: { a: 12, c: { a: 12 } },
c: 17
},
{
a: 12,
b: { a: 12, c: { a: 12 } },
c: 17
},
{
a: 12,
b: { a: 12, c: { a: 12 } },
c: 17
}
];
const deepMergeSum = (obj1, obj2) => {
return Object.keys(obj1).reduce((acc, key) => {
if (typeof obj2[key] === 'object') {
acc[key] = deepMergeSum(obj1[key], obj2[key]);
} else if (obj2.hasOwnProperty(key) && !isNaN(parseFloat(obj2[key]))) {
acc[key] = obj1[key] + obj2[key]
}
return acc;
}, {});
};
const result = arr.reduce((acc, obj) => acc = deepMergeSum(acc, obj));
console.log('result: ', result);
Try this.
let t1 =
{
a:12,
b:8,
c:17
};
let t2 =
{
a:2,
b:4,
c:1
};
function sum(ob1, ob2) {
let sum = {};
Object.keys(ob1).forEach(key => {
if (ob2.hasOwnProperty(key)) {
sum[key] = ob1[key] + ob2[key]
}
})
return sum;
}
sum(t1, t2);
https://jsfiddle.net/fbnt2vhe/
If the objects have all common keys, you could take the keys from one object in advance and iterate for creating a new result object and later the keys from the single objects.
var o1 = { a: 12, b: 8, c: 17 },
o2 = { a: 2, b: 4, c: 1 },
keys = Object.keys(o1),
result = [o1, o2].reduce(function (r, o) {
keys.forEach(function (k) {
r[k] += o[k];
});
return r;
}, keys.reduce(function (r, k) {
r[k] = 0;
return r;
}, Object.create(null)));
console.log(result);
If you have just two objects:
const x = { a: 12, b: 8, c: 17 }
const y = { a: 2, b: 4, c: 1 }
const z = Object.fromEntries(Object.keys(x).map(k=>[k,x[k]+y[k]]))
console.log(z)
or, if you have many objects:
const arr = [{ a: 33, b: 44, c: 55 }, { a: 12, b: 8, c: 17 }, { a: 2, b: 4, c: 1 }]
const z = Object.fromEntries(Object.keys(arr[0]).map(k=>[k,arr.reduce((s,o)=>s+o[k],0)]))
console.log(z)

Elegant array transformation in Javascript

What's an elegent way - purely functional, ideally - to transform (reduce?) this array:
var in = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
]
Into this:
var out = [
{ a: 1, x: 'foo', y: 'goo' },
{ a: 2, x: 'hoo', y: 'joo' }
]
The logic is that all elements should be joined based on their a property, and all b and c properties denote key/value pairs respectively that should be merged into the single object based on their shared a value.
You can use a hash object, and reduce to wrap the hashing like this:
const arr = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
];
let result = Object.values( // the result is the values of the hash object
arr.reduce((hash, o) => { // hash is a hash object that make it easier to group the result
hash[o.a] = hash[o.a] || {a: o.a}; // if there is no object in the hash that have the value of the key a equal to o.a, then create a new one
hash[o.a][o.b] = o.c; // set the value of the key stored in o.b to o.c
return hash;
}, {})
);
console.log(result);
You could use a closure with a Map
var input = [{ a: 1, b: 'x', c: 'foo' }, { a: 1, b: 'y', c: 'goo' }, { a: 2, b: 'x', c: 'hoo' }, { a: 2, b: 'y', c: 'joo' }],
output = input.reduce((map => (r, o) => (!map.has(o.a) && map.set(o.a, r[r.push({ a: o.a }) - 1]), map.get(o.a)[o.b] = o.c, r))(new Map), []);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use forEach and Object.assign to group by a and then map to return object values.
var data = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
]
var r = {}
data.forEach(e => r[e.a] = Object.assign((r[e.a] || {}), {a: e.a, [e.b]: e.c}))
r = Object.keys(r).map(e => r[e])
console.log(r)
I like provided answers, but here is my attempt. I believe it's more readable, but it uses Object.assign and Object.values
const input = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
]
const map = input.reduce((acc, obj) => {
const [a, key, value] = Object.values(obj)
const newObj = {a, [key]: value}
if (acc[a]) {
Object.assign(acc[a], newObj)
} else {
acc[a] = newObj
}
return acc
}, {})
console.log(Object.values(map))
Not sure if approach is elegant or functional, though returns expected result using for..of loops, Array.prototype.some() and Object.assign()
function props(array, key, prop1, prop2) {
let arr = [];
for (let obj of array) {
let o = {};
for (let {[key]:_key, [prop1]:_prop1, [prop2]:_prop2} of [obj]) {
o[_prop1] = _prop2;
o[key] = _key;
}
if (!arr.some(p => p[key] === o[key])) arr.push(o);
for (let prop of arr) {
if (prop[key] == o[key]) {
prop = Object.assign(prop, o)
}
}
}
return arr
}
var _in = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
];
console.log(props(_in, "a", "b", "c"));

Categories

Resources