Flatten nested objects, keeping the properties of parents - javascript

I have a data structure that has this shape:
[
{
a: "x",
val: [
{ b: "y1", val: [1, 2, 3] },
{ b: "y2", val: [4, 5, 6] },
],
},
];
An example with 3 levels:
[
{
a: "x",
val: [
{ b: "y1", val: [
{c: "z1", val: [1, 2]}
] },
{ b: "y2", val: [
{ c: "z2", val: [3, 4] },
{ c: "z3", val: [5, 6, 7] },
{ c: "z4", val: [8] }
] },
],
},
];
Each object always has the same level of nesting, and I know the max depth of nesting in advance. We also know the names of the keys in advance: we know that the key at level 1 will be named a, the one at level 2 will be named b, and so on.
I'm looking to create a function that transforms the first example into:
[
{
a: "x",
b: "y1",
val: [1, 2, 3],
},
{
a: "x",
b: "y2",
val: [4, 5, 6],
},
];
that is, a flat array with values and keys inherited from parents.
I've got a solution which works for the first example:
const res = [
{
a: "x",
val: [
{ b: "y1", val: [1, 2, 3] },
{ b: "y2", val: [4, 5, 6] },
],
},
].flatMap((x) => x.val.flatMap((d) => ({ a: x.a, ...d })));
console.log(res);
but I'm struggling to turn it into a recursive function.
Thank you in advance for your help!

You could have a look to the arrays and if no objects inside return an object, otherwise map val property by storing other properties.
const
isObject = o => o && typeof o === 'object',
flat = array => {
if (!array.every(isObject)) return { val: array };
return array.flatMap(({ val, ...o }) => {
const temp = flat(val);
return Array.isArray(temp)
? temp.map(t => ({ ...o, ...t }))
: { ...o, ...temp };
});
},
data0 = [{ a: "x", val: [{ b: "y1", val: [1, 2, 3] }, { b: "y2", val: [4, 5, 6] }] }],
data1 = [{ a: "x", val: [{ b: "y1", val: [{ c: "z1", val: [1, 2] }] }, { b: "y2", val: [{ c: "z2", val: [3, 4] }, { c: "z3", val: [5, 6, 7] }, { c: "z4", val: [8] }] }] }];
console.log(flat(data0));
console.log(flat(data1))
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

map array of objects based on set of properties

Suppose I have an object:
let array = [
{a: 1, b: 5, c: 9},
{a: 2, b: 6, c: 10},
{a: 3, b: 7, c: 11},
{a: 4, b: 8, c: 12}
];
then I have a dictionary:
const columns = [
{ key: 'a', value: 'a' },
{ key: 'b', value: 'b' },
]
I want to filter out properties that are not defined in columns.
I have tried
array.map((x) => ({"a": x.a, "b": x.b}))
Is there a way to use the data defined in columns instead of manually typing all the properties?
Desired output:
[
{
"a": 1,
"b": 5
},
{
"a": 2,
"b": 6
},
{
"a": 3,
"b": 7
},
{
"a": 4,
"b": 8
}
]
You could map entries and get the new objects.
let
array = [{ a: 1, b: 5, c: 9 }, { a: 2, b: 6, c: 10 }, { a: 3, b: 7, c: 11 }, { a: 4, b: 8, c: 12 }],
columns = [{ key: 'a', value: 'a' }, { key: 'b', value: 'b' }],
keys = columns.map(({ key }) => key),
result = array.map(o => Object.fromEntries(keys.map(k => [k, o[k]])));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use this.
This uses just an array to hold the desired columns because I don't get why you would use a dictionary with key and value being the same.
let array = [
{ a: 1, b: 5, c: 9 },
{ a: 2, b: 6, c: 10 },
{ a: 3, b: 7, c: 11 },
{ a: 4, b: 8, c: 12 },
];
const desiredColumns = ["a", "b"];
const transformed = array.map(item => {
const obj = {};
desiredColumns.forEach(col => {
if(col in item){
obj[col] = item[col];
}
})
return obj;
})
console.log(array);
console.log(transformed)
Another, slightly less direct way using map() and reduce():
Create an array with all the keys we'll keep
Reduce the array to get the desired result
Add current key + value if key keep array
const array = [{a: 1, b: 5, c: 9}, {a: 2, b: 6, c: 10}, {a: 3, b: 7, c: 11}, {a: 4, b: 8, c: 12} ];
const columns = [{ key: 'a', value: 'a' }, { key: 'b', value: 'b' }, ];
const toKeep = columns.map(({ key }) => key).flat();
const result = array.map(a =>
Object.keys(a)
.reduce((prev, cur) => (toKeep.includes(cur)) ? { ...prev, [cur]: a[cur] } : prev, {})
);
console.log(result);
Result:
[
{
"a": 1,
"b": 5
},
{
"a": 2,
"b": 6
},
{
"a": 3,
"b": 7
},
{
"a": 4,
"b": 8
}
]

How grouped sort array of objects based on object's value?

I have array of objects like this:
var points = [{a: 4, b: 3, c:'parent'}, {a: 1, b:5, c:'child'}, {a: 5, b: 2, c:'child'}, {a: 1, b: 2, c:'parent'}, {a: 3, b: 1, c:'child'}];
I need to sort it on "a" object's value. But the problem is if it has the "c" object's value "parent" the "c" object's value "child" should be next object of that and shouldn't be sorted.
At last I expect to have this sorted points array:
var sortedPoints = [{a: 1, b: 2, c:'parent'}, {a: 3, b: 1, c:'child'} ,{a: 4, b: 3, c:'parent'}, {a: 1, b:5, c:'child'}, {a: 5, b: 2, c:'child'}];
You need to group parents and children, sort and get a flat array.
var points = [{ a: 4, b: 3, c: 'parent' }, { a: 1, b: 5, c: 'child' }, { a: 5, b: 2, c: 'child' }, { a: 1, b: 2, c: 'parent' }, { a: 3, b: 1, c: 'child' }],
sorted = points
.reduce((r, o) => {
if (o.c === 'parent') r.push([o]);
else r[r.length - 1].push(o);
return r;
}, [])
.sort(([{ a }], [{ a: b }]) => a - b)
.flat();
console.log(sorted);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to reshaping Data in Javascript from array to object without losing some of the data

I am trying to add the key to each so that I can be able to easy make a multi scatter plot in d3. . I am not sure how to do it.
EDIT: TO CLARIFY what I meant.
Data:
var dataOriginal = {
Apples: [{"A":4,"B":null,"C":null,"D":2}, {"A":5,"B":null,"C":3,"D":2}],
Oranges: [{"A":3,"B":1,"C":4,"D":4.3}],
Jackfruit: [{"A":5,"B":4,"C":4,"D":3}],
Avocado: [{"A":null,"B":33,"C":2,"D":9.66}],
Pomegranate: [{"A":5,"B":3.5,"C":null,"D":6}]
}
Function:
const data = Object.keys(dataOriginal).map((key) => {
const temp = {...dataOriginal[key]};
temp.key = key;
return temp;
});
Results:
0:
0: {A: 4, B: null, C: null, D: 2}
1: {A: 5, B: null, C: 3, D: 2}
key: "Apples"
__proto__: Object
1:
0: {A: 3, B: 1, C: 4, D: 4.3}
key: "Oranges"
__proto__: Object
2:
0: {A: 5, B: 4, C: 4, D: 3}
key: "Jackfruit"
__proto__: Object
3:
0: {A: null, B: 33, C: 2, D: 9.66}
key: "Avocado"
__proto__: Object
4: {0: {…}, key: "Pomegranate"}
Desired results
: {A: 4, B: null, C: null, D: 2, key: "Apples"}
1: {A: 3, B: 1, C: 4, D: 4.3, key: "Oranges"}
2: {A: 5, B: 4, C: 4, D: 3, key: "Jackfruit"}
3: {A: null, B: 33, C: 2, D: 9.66, key: "Avocado"}
4: {A: 5, B: 3.5, C: null, D: 6, key: "Pomegranate"}
5: {A:5,B:null,C:3,D:2, key: "Apples"}
You need to reduce the object to get a single object with added values.
const
addByKey = array => array.reduce((a, b) => {
Object.entries(b).forEach(([k, v]) => a[k] = (a[k] || 0) + v);
return a;
}, {}),
dataOriginal = { Apples: [{ A: 4, B: null, C: null, D: 2 }, { A: 5, B: null, C: 3, D: 2 }], Oranges: [{ A: 3, B: 1, C: 4, D: 4.3 }], Jackfruit: [{ A: 5, B: 4, C: 4, D: 3 }], Avocado: [{ A: null, B: 33, C: 2, D: 9.66 }], Pomegranate: [{ A: 5, B: 3.5, C: null, D: 6 }] }
data = Object.keys(dataOriginal).map((key) => ({ ...addByKey(dataOriginal[key]), key }));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For getting single object with same keys, you could map the objects, add the key and get a flat array.
const
dataOriginal = { Apples: [{ A: 4, B: null, C: null, D: 2 }, { A: 5, B: null, C: 3, D: 2 }], Oranges: [{ A: 3, B: 1, C: 4, D: 4.3 }], Jackfruit: [{ A: 5, B: 4, C: 4, D: 3 }], Avocado: [{ A: null, B: 33, C: 2, D: 9.66 }], Pomegranate: [{ A: 5, B: 3.5, C: null, D: 6 }] }
data = Object
.keys(dataOriginal)
.flatMap(key => dataOriginal[key].map(o => ({ ...o, key })));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The reason why {"A":5,"B":null,"C":3,"D":2} is missed is because, index 0 is hardcoded in the code.
const temp = {...dataOriginal[key][0]};
Alternate solution:
var dataOriginal = {
Apples: [{"A":4,"B":null,"C":null,"D":2}, {"A":5,"B":null,"C":3,"D":2}],
Oranges: [{"A":3,"B":1,"C":4,"D":4.3}],
Jackfruit: [{"A":5,"B":4,"C":4,"D":3}],
Avocado: [{"A":null,"B":33,"C":2,"D":9.66}],
Pomegranate: [{"A":5,"B":3.5,"C":null,"D":6}]
}
const myData =[]
Object.keys(dataOriginal).map((key) => {
for (let i = 0; i < dataOriginal[key].length; i++) {
myData.push({...dataOriginal[key][i], key})
}
})
console.log(myData)

Transform or normalize nested JSON using lodash

I am trying to transform a nested structure, using the library of lodash, I have achieved the expected result, but they are not functional if the structure changes, so I come to you to help me make more robust the function that transforms the JSON.
the initial structure looks like this
const data = {
foo: {
bar: {
baz: [{ a: 1, b: 2, c: 3 }]
},
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
},
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
bar: {
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
baz: {
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
}
};
after being transformed
const transform = [
{
name: 'barfoo',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'bazfoo',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'foofoo',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'bazbar',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'foobaz',
results: [{ a: 1, b: 2, c: 3 }]
}
];
The idea of the transformation is to join the nested key of the first level with the key of the parent node to generate the value of name in the new object and the value of the object in the 2 level as the value of results
for example for the first iteration of foo object in data
name = key(foo.bar) + key(foo)
results = value(foo.bar.baz)
name = 'barfoo'
results = [{ a: 1, b: 2, c: 3 }]
name = key(foo.baz) + key(foo)
results = value(foo.baz.bar)
name = 'bazfoo'
results = [{ a: 1, b: 2, c: 3 }]
name = key(foo.foo) + key(foo)
results = value(foo.foo.bar)
name = 'foofoo'
results = [{ a: 1, b: 2, c: 3 }]
and so with the other objects that are inside data.
I'm not sure if the structure will ever vary, but I added a few extra test cases so you can see how this will behave in some additional scenarios.
const data = {
foo: {
bar: {
baz: [{ a: 1, b: 2, c: 3 }]
},
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
},
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
bar: {
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
baz: {
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
a1: {
a2: [{ a: 1, b: 2, c: 3 }]
},
b1: [{ a: 1, b: 2, c: 3 }],
c1: {
c2: {
c3: {
c4: [{ a: 1, b: 2, c: 3 }]
}
},
c5: [{ a: 1, b: 2, c: 3 }]
},
d1: {
d2: {
d3: undefined
}
},
e1: {
e2: {
e3: null
}
},
f1: {
f2: {
// Ignored
}
}
};
function transformObject(object, name) {
if (!name) {
name = "";
}
return _.flatten(_.map(object, function(value, key) {
if (typeof value === "undefined"
|| value === null
|| _.isArray(value)) {
return {
name: name,
results: value
}
}
var objectName = key + name;
return transformObject(value, objectName);
}));
}
transformObject(data);

Adding a new element to an object in an array

Lets say I have and array made up of objects:
var points = [
{ id: 1, a: 0, b: 3 },
{ id: 2, a: 4, b: -1 },
{ id: 3, a: -1, b: 5 },
{ id: 4, a: 41, b: 2 },
{ id: 5, a: 69, b: 3 },
]
I want to iterate through each item and add a + b to get a new item d. I then want to add d within each object in the array to get a new value. When I try the below, it just adds 5 extra objects rather than appending the new element (key=value, ex: d: 3) to each individual object. What am I doing wrong here?
points.forEach((item) => {
var d = Math.abs(item.x) + Math.abs(item.y);
console.log(d);
points.item.push('d: ' + d);
});
Try following
var points = [{ id: 1, a: 0, b: 3 },{ id: 2, a: 4, b: -1 },{ id: 3, a: -1, b: 5 },{ id: 4, a: 41, b: 2 },{ id: 5, a: 69, b: 3 }];
points.forEach(o => o.d = Math.abs(o.a) + Math.abs(o.b));
console.log(points);
#jcbridwe, you can use assign() method on Object to add missing property from source object to target object.
Please have a look at the below code.
Try the below code online at http://rextester.com/EPHYV10615.
var points = [
{ id: 1, a: 0, b: 3 },
{ id: 2, a: 4, b: -1 },
{ id: 3, a: -1, b: 5 },
{ id: 4, a: 41, b: 2 },
{ id: 5, a: 69, b: 3 },
]
for(var index in points){
var a = points[index].a;
var b = points[index].b;
Object.assign(points[index], {d: a+b});
}
console.log(points);
» Output
[ { id: 1, a: 0, b: 3, d: 3 },
{ id: 2, a: 4, b: -1, d: 3 },
{ id: 3, a: -1, b: 5, d: 4 },
{ id: 4, a: 41, b: 2, d: 43 },
{ id: 5, a: 69, b: 3, d: 72 } ]
Mutable approach:
points.forEach(o => o.d = o.a + o.b);
Immutable approach:
const newPoints = points.map(o => Object.assign({}, o, {d: o.a + o.b}))

Categories

Resources