How to flatten object of objects recursively into array - javascript

I have an object like this:
const myObj = {
a: {
b: {
c: 1,
d: 2
},
f: {
z: 4,
u: 6
}
}
}
into this:
const myObj = [
{
c: 1,
d: 2,
},
{
z: 4,
u: 6,
}
]
I found this: How to recursively transform an array of nested objects into array of flat objects? but the original is an array of objects, and mine is an object itself.

You can traverse the values of the objects until you reach the leaves (objects with no values that are other objects).
const myObj = {
a: {
b: {
c: 1,
d: 2
},
f: {
z: 4,
u: 6
}
}
};
const flatObj = o => Object.values(o).some(x => x === Object(x)) ?
Object.values(o).flatMap(flatObj) : [o];
console.log(flatObj(myObj))

Related

Calculating sum of properties of multiple objects

So I have an array of objects with some varying number of properties (but the property names are known), for example:
let data = [{a: 10, b: 1, c:10},
{a: 17, b: 2, c:16},
{a: 23, b: 3, c:41}]
I need to construct an object that sums up the values in the respective properties, so in this example I'd need to construct an object {a: 50, b: 6, c:67}
I wrote the following function to do this:
calcTotalForDataProps(data, props) {
let summedData = {}
for (const prop of props) {
summedData[prop] = 0;
}
data.forEach((dataObj) => {
for (const prop of props) {
summedData[prop] += dataObj[prop];
}
});
return summedData;
}
And you call it like this:
calcTotalForDataProps(data, ['a', 'b', 'c']);
But I'm wondering if there's a much shorter way to write this with ES6?
You could map the wanted props with their new sums for getting an object as result.
function calcTotalForDataProps(data, props) {
return data.reduce((r, o) => Object
.fromEntries(props.map(k => [k, (r[k] || 0) + o[k]])
), {});
}
const data = [{ a: 10, b: 1, c: 10}, { a: 17, b: 2, c: 16 }, { a: 23, b: 3, c: 41 }]
console.log(calcTotalForDataProps(data, ['a', 'b', 'c']));
There's no need to iterate over the properties initially - you can create the property inside the other loop if it doesn't exist yet.
let data = [{a: 10, b: 1, c:10},
{a: 17, b: 2, c:16},
{a: 23, b: 3, c:41}]
const calcTotalForDataProps = (data, props) => {
const summedData = {};
for (const obj of data) {
for (const [prop, num] of Object.entries(obj)) {
summedData[prop] = (summedData[prop] || 0) + num;
}
}
return summedData;
}
console.log(calcTotalForDataProps(data));

How can I unite multiple objects to add their keys?

Implement the sumObjects function, which takes an unlimited number of objects and returns an object that combines all the objects entered.
Notes:
All object properties will have numeric values only
If the object keys match, the values of the corresponding keys are summed
The function always returns an object
The numbers in the object can be positive or negative
You can use the rest operator to collect arguments passed to a function in one array
Examples:
const obj = {};
sumObjects() === {}
sumObjects(obj) === {}
and
const first = {a: 2, b: 4};
const second = {a: 2, b: 10};
const third = {d: -5};
sumObjects(first) === {a: 2, b: 4}
sumObjects(first, third) === {a: 2, b: 4, d: -5}
sumObjects(first, second, third) === {a: 4, b: 14, d: -5}
This is my code.
function sumObjects(...params) {
let C = Object.fromEntries(
Object.keys(params[0])
.concat(Object.keys(params[1]))
.map(k => [k,
(params[0][k] || 0) + (params[1][k])
])
)
return C
}
I don't know how to add all these objects into one.
The bottom line is that I need to combine all the objects, but I just don't know how to do this, I can't find anything.
You can iterate over the param objects using .reduce and for each object, set/update its properties in acc:
function sumObjects(...params) {
return params.reduce((acc,item) => {
Object.entries(item).forEach(([property,value]) => {
const prev = acc[property];
acc[property] = prev ? prev+value : value;
});
return acc;
}, {});
}
const first = { a: 2, b: 4 };
const second = { a: 2, b: 10 };
const third = { d: -5 };
console.log( sumObjects(first) );
console.log( sumObjects(first, third) );
console.log( sumObjects(first, second, third) );
You could reduce the array of objects and iterate the entries of a single object.
const
sumObjects = (...objects) => objects.reduce((r, o) => {
Object.entries(o).forEach(([k, v]) => r[k] = (r[k] ||0) + v);
return r;
}, {}),
first = { a: 2, b: 4 },
second = { a: 2, b: 10 },
third = { d: -5 };
console.log(sumObjects(first)); // {a: 2, b: 4}
console.log(sumObjects(first, third)); // {a: 2, b: 4, d: -5}
console.log(sumObjects(first, second, third)); // {a: 4, b: 14, d: -5}
.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);

Add new property to object collection based on condition with lodash

I have an array of objects as such:
var data = [{ a: 1 },
{ a: 1 },
{ a: 2 },
{ a: 2 }];
How can I create, with lodash, a new array just like data but for which we added a new property b: 1 to each object that has the property a equal to 1?
The new array should be like this:
var newdata = [{ a: 1, b: 1 },
{ a: 1, b: 1 },
{ a: 2 },
{ a: 2 }];
I guess we could combine both _.assign and _.filer but I'm not sure how.
You can do this with lodash via either _.defaults or _.assign / _.assignIn / _.extend:
var data = [{ a: 1 },{ a: 1 },{ a: 2 },{ a: 2 }];
console.log(_.map(data, x => x.a==1 ? _.defaults(x, {b: 1}) : x))
console.log(_.map(data, x => x.a==1 ? _.assign(x, {b: 1}) : x))
console.log(_.map(data, x => x.a==1 ? _.assignIn(x, {b: 1}) : x))
console.log(_.map(data, x => x.a==1 ? _.extend(x, {b: 1}) : x))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You could map the array and use a new property if the condition is true.
var data = [{ a: 1 }, { a: 1 }, { a: 2 }, { a: 2 }],
newData = data.map(o => Object.assign({}, o, o.a === 1 && { b: 2 }));
console.log(newData);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you're happy with vanilla Javascript, this here works:
var data = [{ a: 1 },
{ a: 1 },
{ a: 2 },
{ a: 2 }];
var newData = [];
newData = data.reduce((acc, el) => {
return el.a === 1 ? acc.concat({a: el.a, b: 1}) : acc.concat(el);
}, []);
console.log(newData);
// expected output: [{a: 1, b: 1},
// {a: 1, b: 1},
// {a: 2},
// {a: 2}]
You can generate a function with lodash by applying _.cond() to _.map() with _.partialRight.
Use _.matches() to detect if the object has a: 1 property, and _.assign() { b: 1 } if it does.
When a is not 1, use _.stubTrue() to always return the original object via _.identity():
var func = _.partialRight(_.map, _.cond([
[_.matches({ 'a': 1 }), o => _.assign({}, o, { b: 2 })],
[_.stubTrue, _.identity]
]));
var data = [{ a: 1 },{ a: 1 },{ a: 2 },{ a: 2 }];
const result = func(data);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

Return an Array of Arrays containing objects that share a common value in a property

Say I have an array of 3 objects like this:
[
{
a: 4,
b: 5,
c: 4
},
{
a: 3,
b: 5,
c: 6
},
{
a: 2,
b: 3,
c: 3
}
]
I would like to return an array of arrays containing the objects that share a common value for the property b. So the resulting array would contain only one array containing 2 objects like this:
[
[
{
a: 4,
b: 5,
c: 4
},
{
a: 3,
b: 5,
c: 6
}
]
]
How would I do this?
You could do this with map and filter
var data = [{"a":4,"b":5,"c":4},{"a":3,"b":5,"c":6},{"a":2,"b":3,"c":3}];
var check = data.map(e => {return e.b});
var result = [data.filter(e => { return check.indexOf(e.b) != check.lastIndexOf(e.b)})];
console.log(result)
To group multiple objects in separate arrays with same b values you can use map and forEach
var data = [{"a":4,"b":5,"c":4},{"a":3,"b":5,"c":6},{"a":2,"b":3,"c":3}, {"a":3,"b":7,"c":6},{"a":2,"b":7,"c":3}], result = [];
var check = data.map(e => {return e.b});
data.forEach(function(e) {
if(check.indexOf(e.b) != check.lastIndexOf(e.b) && !this[e.b]) {
this[e.b] = [];
result.push(this[e.b]);
}
(this[e.b] || []).push(e);
}, {});
console.log(result)
This proposal uses a single loop with Array#forEach but without Array#indexOf.
var array = [{ a: 4, b: 5, c: 4 }, { a: 3, b: 5, c: 6 }, { a: 2, b: 3, c: 3 }],
grouped = [];
array.forEach(function (a) {
this[a.b] = this[a.b] || [];
this[a.b].push(a);
this[a.b].length === 2 && grouped.push(this[a.b]);
}, Object.create(null));
console.log(grouped);
You can create a function that accepts fulfillment criteria and will return as many nested arrays as rules passed.
Let's say you have an array of objects, arr.
var arr = [{a: 1, b: 2}, {a: 3, b: 2}, {a: 3, b: 4}, {a: 1, b: 1}]
And you want to return an array with with nested arrays that fulfill a particular requirement, let's say you want objects with an a:1 and b:2.
You can create a function that loops through your rules and creates a nested array with the objects that fulfill each rule.
For example:
var arr = [{a: 1, b: 2}, {a: 3, b: 2}, {a: 3, b: 4}, {a: 1, b: 1}]
function makeNestedArrays() {
var rules = [].slice.call(arguments);
return rules.reduce(function(acc, fn) {
var nestedArr = [];
arr.forEach(function(obj) {
if (fn(obj)) {
nestedArr.push(obj);
}
});
// only push nested array
// if there are matches
if (nestedArr.length) {
acc.push(nestedArr);
}
return acc;
}, []);
}
var result = makeNestedArrays(
function(obj) { return obj.a === 1; },
function(obj) { return obj.b === 2; }
);
console.log(result);
This allows you to pass as many "rules" as you want, and will create a nested array for each rule so long as there is at least one match.
You could use a Map to group them, this should work with any kind of value (just be sure the equality rules check out):
var arr = [{
a: 4,
b: 5,
c: 4
}, {
a: 3,
b: 5,
c: 6
}, {
a: 2,
b: 3,
c: 3
}];
var result = arr.reduce(function(m, o){
var value = o.b;
if(m.has(value)){
m.get(value).push(o);
} else {
m.set(value, [o]);
}
return m;
}, new Map());
console.log(...(result.values()));
If you'd need to filter out the groups of 1:
var arr = [{
a: 4,
b: 5,
c: 4
}, {
a: 3,
b: 5,
c: 6
}, {
a: 2,
b: 3,
c: 3
}];
var result = arr.reduce(function(m, o){
var value = o.b;
if(m.has(value)){
m.get(value).push(o);
} else {
m.set(value, [o]);
}
return m;
}, new Map());
result = [...result.values()].filter(a => a.length > 1);
console.log(result);

Categories

Resources