How to flatten an Object of Objects into an Array of Objects? - javascript

I have data in the form of an Object of Objects
{
a: {
x: 1,
y: 1
},
b: {
x: 10,
y: 10
}
}
I would like to transform it into an Array of Objects, each Object being formed from the key and values of the original one. In my case what would hold the key, and value_x the content of x in the value (of the original object):
[{
value_x: 1,
what: "a"
}, {
value_x: 10,
what: "b"
}]
I can do that via
o = {
a: {
x: 1,
y: 1
},
b: {
x: 10,
y: 10
}
}
a = Object.entries(o).map(e => {
return {
what: e[0],
value_x: e[1].x
}
})
console.log(a)
It works but seem quite ugly. Coming from a Python background, I was hoping to be able to get directly the key and value elements via something like
a = Object.entries(o).map([k, v] => {return {what: k, value_x: v.x}})
or
a = Object.entries(o).map((k, v) => {return {what: k, value_x: v.x}})
but none of them work.
Is there a better solution? (= more aligned with the language)

You could take the entries for the nested object as well with a flatMap approach.
const
o = { a: { x: 1, y: 1 }, b: { x: 10, y: 10 } },
a = Object
.entries(o)
.flatMap(([what, values]) => Object
.entries(values)
.map(([k, v]) => ({ what, [`value_${k}`]: v }))
);
console.log(a);

let obj = {
a: {
x: 1,
y: 1
},
b: {
x: 10,
y: 10
}
};
console.log(
Object.entries(obj).map(([key,{x}])=>({value_x: x,what: key}))
);

Related

Flatten nested JavaScript object

I have a nested object and I want to flatten/map it into a single-layered, table-like object.
[{
a: 1,
b: 2,
c: [{
x: 10,
y: 20
}, {
x: 30,
y: 40
}]
}, {
a: 3,
b: 4,
c: [{
x: 50,
y: 60
}, {
x: 70,
y: 80
}]
}]
From that, I want to get something like this:
[{
a: 1,
b: 2,
x: 10,
y: 20
}, {
a: 1,
b: 2,
x: 30,
y: 40
}, {
a: 3,
b: 4,
x: 50,
y: 60
}, {
a: 3,
b: 4,
x: 70,
y: 80
}]
Sure, I could simply iterate over the object with two for loops and put the result info a separate array, but I wonder, if there is a simpler solution. I already tried to play around with flatMap. It works, if I only want the c portion of my nested object, but I don't know how to map a and b to this object.
As some of you asked for some working code, this should do it (untested):
let result = [];
for (const outer of myObj)
for (const inner of outer.c)
result.push({a: outer.a, b: outer.b, x: inner.x, y: inner.y});
The question is, if there is a functional one-liner or even another, better approach. In reality, my object consists of four layers and the nested for loops become messy quite fast.
You may use flatMap method alongwith map on property 'c':
var input = [{ a: 1, b: 2, c: [{ x: 10, y: 20 }, { x: 30, y: 40 }] }, { a: 3, b: 4, c: [{ x: 50, y: 60 }, { x: 70, y: 80 }] }];
const output = input.flatMap(obj =>
obj.c.map(arr => ({a: obj.a, b: obj.b, x: arr.x, y: arr.y}))
);
console.log(output);
Ideally a solution would require something to tell how far down to start classing the object as been a full object, a simple solution is just to pass the level you want. If you don't want to pass the level, you could do a check and if none of the properties have array's, then you would class this as a complete record, but of course that logic is something you would need to confirm.
If you want a generic version that works with multiple levels were you pass the level & using recursion you could do something like this ->
const a=[{a:1,b:2,c:[{x:10,y:20},{x:30,y:40}]},{a:3,b:4,c:[{x:50,y:60},{x:70,y:80}]}];
function flattern(a, lvl) {
const r = [];
function flat(a, l, o) {
for (const aa of a) {
o = {...o};
for (const [k, v] of Object.entries(aa)) {
if (Array.isArray(v) && l < lvl) flat(v, l + 1, o);
else o[k] = v;
}
if (l === lvl) r.push(o);
}
}
flat(a, 1);
return r;
}
console.log(flattern(a, 2));
//console.log(flattern(a, 1));
A flatMap solution would look like this:
const result = myObj.flatMap(outer =>
outer.c.map(inner =>
({a: outer.a, b: outer.b, x: inner.x, y: inner.y})
)
);
Of course, if your object has multiple layers, not just two, and possibly even multiple or unknown properties that have such a nesting, you should try to implement a recursive solution. Or an iterative one, where you loop over an array of property names (for your example case, ["c"]) and apply the flattening level by level.
One of the solution using reduce is:
const list = [{
a: 1,
b: 2,
c: [{
x: 10,
y: 20
}, {
x: 30,
y: 40
}]
}, {
a: 3,
b: 4,
c: [{
x: 50,
y: 60
}, {
x: 70,
y: 80
}]
}]
const flatten = (arr) => {
return arr.reduce((flattened, item) => {
return [
...flattened,
...item.c.reduce((flattenedItem, i) => {
return [
...flattenedItem,
{
a: item.a,
b: item.b,
x: i.x,
y: i.y
}
]
}, [])
]
}, [])
}
console.log(flatten(list));
Using two reducers to flatten your structure
const input = [{
a: 1,
b: 2,
c: [{
x: 10,
y: 20
}, {
x: 30,
y: 40
}]
}, {
a: 3,
b: 4,
c: [{
x: 50,
y: 60
}, {
x: 70,
y: 80
}]
}]
const result = input.reduce((acc_0, x) => {
return [...acc_0, ...x.c.reduce((acc_1, y) => {
const obj = {
a: x.a,
b: x.b,
x: y.x,
y: y.y
}
acc_1.push(obj);
return acc_1;
}, [])]
}, []);
console.log(result)

Summarize the frequency of array of objects

Assume I have the following array of objects.
data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
]
what I need is to summarize the frequency of identical object in the array. The output will look like:
summary = [
{ x: 1, y: 1, f: 3 },
{ x: 1, y: 2, f: 1 },
{ x: 2, y: 2, f: 2 },
{ x: 3, y: 3, f: 1 }
]
For now I have this code
const summary = data.map((item, index, array) => {
return { x: item.x, y: item.y, f: array.filter(i => i === item).length };
});
But I suppose I can do better by using reduce or includes. Any ideas?
Reduce into an object whose keys uniquely represent an object, whose values are the object (with x, y, and f properties). On each iteration, increment the appropriate key's f property, or create the key on the accumulator if it doesn't exist yet:
const data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
const countObj = data.reduce((a, obj) => {
const objString = obj.x + '_' + obj.y;
if (!a[objString]) {
a[objString] = { ...obj, f: 1 };
} else {
a[objString].f++;
}
return a;
}, {});
const output = Object.values(countObj);
console.log(output);
Don't use map - you're better off using reduce like so:
const summary = Object.values(data.reduce((a, { x, y }) => {
a[`${x}-${y}`] = a[`${x}-${y}`] || { x, y, f: 0 };
a[`${x}-${y}`].f++;
return a;
}, {}));
Object.values(data.reduce((sum, i) => {
i_str = JSON.stringify(i); // objects can't be keys
sum[i_str] = Object.assign({}, i, {f: sum[i_str] ? sum[i_str].f+1 : 1});
return sum;
}, {}));
Note:
This snippet will work on an array of any arbitrary objects, as long as they are stringifiable.
Results are not ordered, since object keys aren’t ordered. If this is an issue, sort at will.
What you’re doing, is counting the times an object exists in an array. You probably want results external to the objects, as opposed to embedded in them. Something along these lines might be more manageable, returning a mapping of descriptions of the objects to a count:
data.reduce((sum, i) => {
i_str = JSON.stringify(i); // objects can't be keys
sum[i_str] = sum[i_str] ? sum[i_str]+1 : 1;
return sum;
}, {});
A simple solution based on Array#reduce would be as detailed below:
const data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
const summary = data.reduce((frequencySummary, item) => {
/* Find a match for current item in current list of frequency summaries */
const itemMatch = frequencySummary.find(i => i.x === item.x && i.y === item.y)
if(!itemMatch) {
/* If no match found, add a new item with inital frequency of 1 to the result */
frequencySummary.push({ ...item, f : 1 });
}
else {
/* If match found, increment the frequency count of that match */
itemMatch.f ++;
}
return frequencySummary;
}, []);
console.log(summary)
I know using reduce is probably better, but I tend to use forEach and findIndex for better readability.
var data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
var summary = [];
data.forEach(function(d){
var idx = summary.findIndex(function(i){
return i.x === d.x && i.y === d.y;
});
if(idx < 0){
var sum = Object.assign({}, d);
sum.f = 1;
summary.push(sum);
} else {
summary[idx].f = summary[idx].f + 1;
}
});
console.log(summary);
Create nested objects. The outer object uses x values as keys, the nested object contains y values as keys, and the values are the frequencies.
data = [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 1, y: 2 },
{ x: 1, y: 1 }
];
const nested = data.reduce((a, {x, y}) => {
a[x] = a[x] || {};
a[x][y] = a[x][y] ? a[x][y] + 1 : 1
return a;
}, {});
const summary = [];
Object.keys(nested).forEach(x => Object.keys(nested[x]).forEach(y => summary.push({x, y, f: nested[x][y]})));
console.log(summary);
You can use reduce and Map, club the use x and y as key, on every iteration check if the same key is already present on Map than just increase f count by 1 if not than set it to 1
const data = [{ x: 1, y: 1 },{ x: 2, y: 2 },{ x: 3, y: 3 },{ x: 2, y: 2 },{ x: 1, y: 1 },{ x: 1, y: 2 },{ x: 1, y: 1 }];
const countObj = data.reduce((a, obj) => {
const objString = obj.x + '_' + obj.y;
let value = a.get(objString) || obj
let f = value && value.f || 0
a.set(objString, { ...value, f: f+1 })
return a;
}, new Map());
console.log([...countObj.values()]);

Add object and array for same properties

var obj = {x:{y: {a: 1, b:2}}, p: 11}
var arr = [{x: {y: {c: 3}}},{x: {y: {d: 4}}}]
it can be done by lodash merge(obj, ...arr)
but I don't want to use lodash merge method
outputObj = {x:{y: {a: 1, b:2, c: 3, d: 4}}, p: 11}
You could take an iterative and recursive approach and check the type of the value and take either an array or object if a parent property is not given.
function merge(target, ...source) {
source.forEach(s => Object.entries(s).forEach(([k, v]) => {
if (v && typeof v === 'object') {
merge(target[k] = target[k] || (Array.isArray(v) ? [] : {}), v);
} else {
target[k] = v;
}
}));
}
var obj = { x: { y: { a: 1, b: 2 } }, p: 11 },
arr = [{ x: { y: { c: 3 } } }, { x: { y: { d: 4 } } }]
merge(obj, ...arr)
console.log(obj);
I found you can do this in "one line" using recursive reduction
const
merge = (target, obj) =>
Object.keys (obj).reduce((merged, key) => ({
...merged,
[key]:[obj[key]].reduce(merge, target[key])||obj[key]
}), target),
merged = arr.reduce (merge,obj);
console.log (merged);
<script>
var obj = {x:{y: {a: 1, b:2}}, p: 11}
var arr = [{x: {y: {c: 3}}},{x: {y: {d: 4}}}]
</script>

Using reduce to add values of properties of a collection objects in JavaScript

Pretty straight forward:
var bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
var reduce = bar.reduce((acc, item) => {
acc['a'] = item.a++;
acc['b'] = item.b++
return acc;
}, {});
console.log(reduce);
{a: 10, b: 20}
I'd like reduce assigned the reference: {a:20, b: 40}
Here is a general solution that will work even if your object inside your array contains different properties.
var bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
var reduce = bar.reduce((acc, item) => {
for (let [key, value] of Object.entries(item)){
if( acc.hasOwnProperty(key)) {
acc[key] += value
}
else {
acc = {...acc, [key]: value }
}
}
return acc;
}, {});
console.log(reduce);
Rather than assigning the accumulator's property the item's property incremented by one, you should add to the existing accumulator's property value. You also shouldn't pass an initial object to the reduce given this implementation (or, if you do, you'll need to define the a and b properties).
Since you're using reduce, I think you should also consider using const instead of var - const is less bug-prone and easier to read:
const bar = [
{ a: 10, b: 20 }, { a: 10, b: 20 }
];
const reduced = bar.reduce((acc, item) => {
acc.a += item.a;
acc.b += item.b;
return acc;
});
console.log(reduced);
You could return a new object with added values.
var bar = [{ a: 10, b: 20 }, { a: 10, b: 20 }],
reduce = bar.reduce((a, b) => ({ a: a.a + b.a, b: a.b + b.b }));
console.log(reduce);
Or with a complete dynamic approach for all properties.
const add = (a, b) =>
Object.assign({}, a, ...Object.entries(b).map(([k, v]) => ({ [k]: a[k] + v })));
var bar = [{ a: 10, b: 20 }, { a: 10, b: 20 }],
reduce = bar.reduce(add);
console.log(reduce);

How Group JSON Array Data By multiple keys

How can I group JSON Array by multiple Keys in Nodejs.
Is there any solution with underscore or lodash (does not matter if its plain javascript)?
Array:
[{ key: '2017-1', y: 1, gkey: 'y' },
{ key: '2017-1', x: 1, gkey: 'x' }]
Expected Result :
[{ key: '2017-1', x: 1, y:1 }]
You can use reduce and Object.values
let arr = [
{ key: '2017-1', y: 1, gkey: 'y' },
{ key: '2017-1', x: 1, gkey: 'x' },
{ key: '2017-2', x: 1, gkey: 'x' },
];
let result = Object.values(arr.reduce((c, {key,gkey,...r}) => {
c[key] = c[key] || {key};
c[key] = Object.assign(c[key], r);
return c;
}, {}));
console.log(result);
If you cant use Object.values on your nodejs version, you can:
let arr = [
{ key: '2017-1', y: 1, gkey: 'y' },
{ key: '2017-1', x: 1, gkey: 'x' },
{ key: '2017-2', x: 1, gkey: 'x' },
];
let temp = arr.reduce((c, {key,gkey,...r}) => {
c[key] = c[key] || {key};
c[key] = Object.assign(c[key], r);
return c;
}, {});
let result = [];
for (let key in temp) result.push(temp[key]);
console.log(result);
if I understood correctly what you need, the reduce function could do what you need.
reduce call the function for each element of the array. arr is the result array who will be return by the reduce at the end. I check for each elem if it's in the arr. if not, I add it.
I did not test this code, but it should work
arrayOfData.reduce((arr, elem) => {
let e = arr.find(el => el.key === elem.key);
if(!e) {
e = {key : elem.key}
arr.push(e);
}
e[elem.gkey] = elem[elem.gkey];
return arr;
}, [])

Categories