So I am trying to automate unpacking a nested Json with arrays inside and stuck at creating duplicate objects if value of key is array with length > 1
How do I do it?
Right now I am trying to achieve it with recursion
[
{
a: '1',
b: [
{
c: '3',
d: '4',
},
{
c: '5'
},
{
c: '7',
d: '8'
}
],
f: [
{
d: '6'
},
{
d: '9'
}
],
e: [
{
g: '9'
}
]
}
]
// Expect
// When creating duplicate object, those keys which repeat I want to mark as undefined, to make JSON lighter
// I also want to add 'key: number' to order those objects
[
{
a: '1',
b.c: '3',
b.d: '4',
f.d: '6',
e.g: '9',
key: 1,
},
{
a: undefined,
b.c: '5',
b.d: undefined,
f.d: '9',
e.g: undefined,
key: 2,
},
{
a: undefined,
b.c: '7',
b.d: '8',
f.d: undefined,
e.g: undefined,
key: 3,
}
]
// Code
function recurseObject(object: any, nestedKeyName: any, obj: any, count: any) {
Object.entries(object).map(([key, dataItem]: [key: string, dataItem: any]) => {
const newKeyName = nestedKeyName ? (nestedKeyName + '.' + key) : key
let currentCount = count
if (Array.isArray(dataItem)) {
obj[newKeyName] = dataItem.map((item: any) => {
const objNested: any = {}
recurseObject(item, newKeyName, objNested, currentCount)
return objNested
})
} else if (isObject(dataItem)) {
obj['key'] = currentCount
recurseObject(dataItem, newKeyName, obj, currentCount + 1)
} else {
obj[newKeyName] = dataItem
}
})
}
function rcBody(data: any): any {
if (Array.isArray(data)) {
let key = 0
return data.map((object: any) => {
const obj: any = {}
recurseObject(object, null, obj, 0)
obj['key'] = key
key += 1
return obj
})
} else if (isObject(data)) {
const obj: any = {}
recurseObject(data, null, obj, 0)
return obj
} else {
return {}
}
}
If the value of key is array of objects with more than one object, then I want to create a duplicate object.
Table I want to generate
I have bad news and good news for you:
First the bad news:
If it is in fact true, that objects/arrays can be nested even further than it is currently the case, then your data saving format doesn't really work. Let me show you why wiht the help of an example:
[
{
t: [
{
a: [
{
b: 1,
c: 2,
},
{ c: 3 },
],
},
{
a: [
{
b: 3,
c: 5,
},
],
},
],
},
];
how would you go about storing this? ... without nesting the return Array/object itself, it is extremely difficult (if not impossible), to store the date how you are wanting to)
How do you want to go about storing cases like this one here? (The example I provided is a quite tame example ... if you add some more nesting/ more objects/ arrays, it gets way more complicated.
Also is the your particular way of storing data even required? Is the way you structure your desired return array/objects relevant in future steps, or would other structures work too?
But nonetheless I have produced 2 functions ... the first one can handle 2 deep paths (so no deeper than the array/object you provided in your example) the array above would NOT work with this function
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction2(arg, currentPath = "", rtnArr = [{}], key = 0) {
if (Array.isArray(arg)) {
arg.forEach((x, index) => myFunction2(x, currentPath, rtnArr, index));
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction2(arg[x], currentPath + "." + x, rtnArr, key));
} else {
rtnArr[key] = rtnArr[key] || {};
rtnArr[key][currentPath.substring(1)] = arg;
}
return rtnArr;
}
console.log(myFunction2(x));
The next function can handle infinitely nested arrays/objects - BUT the return is not quite like you desire - (but still: all paths with value are still present in an array of objects ... and each objects, contains each unique path only once) - only in which object the path value pair appears is different. But it's probably best to just test/read the code to see how it works, what the output will be.
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction(arg, currentPath = "", rtnArr = [{}]) {
if (Array.isArray(arg)) {
arg.forEach((x) => myFunction(x, currentPath, rtnArr));
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction(arg[x], currentPath + "." + x, rtnArr));
} else {
for (let i = 0; i < rtnArr.length; i++) {
if (!rtnArr[i][currentPath.substring(1)]) {
rtnArr[i][currentPath.substring(1)] = arg;
return rtnArr
}
}
rtnArr[rtnArr.length] = {};
rtnArr[rtnArr.length - 1][currentPath.substring(1)] = arg;
}
return rtnArr;
}
console.log(myFunction(x))
At the very least, you know have an idea, how you can solve the problem with recursive functions ... and if my functions don't quite fit your purpose, you at least have a working start point, from where you can tweak and improve the functions, to a point where they fit your needs ... or take the knowledge/ideas you get from reading and understanding my code to write your own.
Edit:
Great news ... I figured out a way where you can have your structure/sorting of data and no restriction on how deeply nested the passed array/object can be.
const x = [
{
a: "1",
b: [
{
c: "3",
d: "4",
},
{
c: "5",
},
{
c: "7",
d: "8",
},
],
f: [
{
d: "6",
},
{
d: "9",
},
],
e: [
{
g: "9",
},
],
},
];
function myFunction2(arg, currentPath = "", rtnArr = [{}], key = []) {
if (Array.isArray(arg)) {
arg.forEach((x, index) => {
key.push(index);
myFunction2(x, currentPath, rtnArr, key);
key.pop();
});
} else if (typeof arg === "object" && arg !== null) {
Object.keys(arg).forEach((x) => myFunction2(arg[x], currentPath + "." + x, rtnArr, key));
} else {
let stringKey = key.reduce((pVal, cVal) => pVal + "." + cVal, "");
if (rtnArr.some((x) => x.key === stringKey)) {
rtnArr.filter((x) => x.key === stringKey)[0][currentPath] = arg;
} else {
rtnArr[rtnArr.length] = { key: stringKey };
rtnArr[rtnArr.length - 1][currentPath] = arg;
}
}
return rtnArr;
}
console.log(myFunction2(x));
Related
Given input:
[{ a: 1 }, { b: 2 }, { c: 3 }]
How to return:
{ a: 1, b: 2, c: 3 }
For arrays it's not a problem with lodash but here we have array of objects.
Use Object.assign:
let merged = Object.assign(...arr); // ES6 (2015) syntax
var merged = Object.assign.apply(Object, arr); // ES5 syntax
Note that Object.assign is not yet implemented in many environment and you might need to polyfill it (either with core-js, another polyfill or using the polyfill on MDN).
You mentioned lodash, so it's worth pointing out it comes with a _.assign function for this purpose that does the same thing:
var merged = _.assign.apply(_, [{ a: 1 }, { b: 2 }, { c: 3 }]);
But I really recommend the new standard library way.
With lodash, you can use merge():
var arr = [ { a: 1 }, { b: 2 }, { c: 3 } ];
_.merge.apply(null, [{}].concat(arr));
// → { a: 1, b: 2, c: 3 }
If you're doing this in several places, you can make merge() a little more elegant by using partial() and spread():
var merge = _.spread(_.partial(_.merge, {}));
merge(arr);
// → { a: 1, b: 2, c: 3 }
Here is a version not using ES6 methods...
var arr = [{ a: 1 }, { b: 2 }, { c: 3 }];
var obj = {};
for(var i = 0; i < arr.length; i++) {
var o = arr[i];
for(var key in o) {
if(typeof o[key] != 'function'){
obj[key] = o[key];
}
}
}
console.log(obj);
fiddle: http://jsfiddle.net/yaw3wbb8/
You can use underscore.extend function like that:
var _ = require('underscore');
var a = [{ a: 1 }, { b: 2 }, { c: 3 }];
var result = _.extend.apply(null, a);
console.log(result); // { a: 1, b: 2, c: 3 }
console.log(a); // [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]
And to prevent modifying original array you should use
var _ = require('underscore');
var a = [{ a: 1 }, { b: 2 }, { c: 3 }];
var result = _.extend.apply(null, [{}].concat(a));
console.log(result); // { a: 1, b: 2, c: 3 }
console.log(a); // [ { a: 1 }, { b: 2 }, { c: 3 } ]
Here can test it
Adding to the accepted answer, a running code snippet with ES6.
let input = [{ a: 1 }, { b: 2 }, { c: 3 }]
//Get input object list with spread operator
console.log(...input)
//Get all elements in one object
console.log(Object.assign(...input))
I've got a neat little solution not requiring a polyfill.
var arr = [{ a: 1 }, { b: 2 }, { c: 3 }];
var object = {};
arr.map(function(obj){
var prop = Object.getOwnPropertyNames(obj);
object[prop] = obj[prop];
});
Hope that helps :)
Here is a nice usage of Object.assign with the array.prototype.reduce function:
let merged = arrOfObjs.reduce((accum, val) => {
Object.assign(accum, val);
return accum;
}, {})
This approach does not mutate the input array of objects, which could help you avoid difficult to troubleshoot problems.
With more modern spread operator
arrOfObj.reduce( (acc, curr) => ({ ...acc, ...cur }) );
You can easily flat your object to array.
function flatten(elements) {
return elements.reduce((result, current) => {
return result.concat(Array.isArray(current) ? flatten(current) : current);
}, []);
};
6 years after this question was asked.
Object.assign is the answer (above) I like the most.
but is this also legal ?
let res = {};
[{ a: 1 }, { b: 2 }, { c: 3 }].forEach(val => {
let key = Object.keys(val);
console.log(key[0]);
res[key] = val[key];
})
const data = [
[{ a: "a" }, { b: "b" }, { c: "c" }],
[{ d: "d" }, { e: "e" }, { f: "f" }],
[{ g: "g" }, { h: "h" }, { i: "i" }],
];
function convertToObject(array){
const response = {};
for (let i = 0; i < array.length; i++) {
const innerArray = array[i];
for (let i = 0; i < innerArray.length; i++) {
const object = innerArray[i];
const keys = Object.keys(object);
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
response[key] = object[key];
}
}
}
return response;
}
console.log(convertToObject(data));
function carParts(manufacturer, model, ...parts) {
return { manufacturer, model, ...Object.assign(...parts) };
}
console.log(
carParts(
"Honda",
"2008",
{ color: "Halogen Lights" },
{ Gears: "Automatic Gears" },
{ LED: "Android LED" },
{ LED: "Android LED1" }
)
);
This is how i have done.
I would like to remove any parent JSON item if the child has empty [], not if the child is empty. The nested JSON has arrays of JSON as well
Here is an example JSON:
{
"hi": 596,
"hello": {
"a" : []
},
"h": {
"a" : ""
},
"hey" : {
"a" : 293,
"b" : "23",
"c" : {
"1" : []
},
"d" : [{
"1" : {
"z" : []
},
"2" : "123"
}]
},
"hola" : 123
}
How I would like the output to look like:
{
"hi": 596,
"h": {
"a" : ""
},
"hey" : {
"a" : 293,
"b" : "23",
"d" : [{
"2" : "123"
}]
},
"hola" : 123
}
How can I do this?
I tried regex JSON.stringify(data).replace(/,\\n\s*\\"(.*?)\\":\s*\[\]*,/g, "") but I believe this only matched the child node. Are there better ways to go about this?
This might require a bit of optimization, but you can use recursion to achieve your goal
const input = {
"hi": 596,
"hello": {
"a": []
},
"h": {
"a": ""
},
"hey": {
"a": 293,
"b": "23",
"c": {
"1": []
}
},
"hola": 123
};
function removeEmpty(obj, parent = null, finalResult = {}) {
Object.keys(obj).forEach(function(key) {
const objKey = obj[key];
if (objKey.constructor === Array && objKey.length === 0) {
return
}
if (typeof objKey === 'object') {
return removeEmpty(objKey, key, finalResult);
} else {
if (parent) {
if (!finalResult[parent]) {
finalResult[parent] = {}
}
finalResult[parent][key] = objKey
} else {
finalResult[key] = objKey;
}
}
});
return finalResult;
}
const output = removeEmpty(input);
console.log(output);
Your question is a bit vague (what should happen if only some children are empty?) and hence I'd recommend using a library. That will make it easier to iterate on your solution as edge cases pop up. Here is how you could solve it using object-scan
// const objectScan = require('object-scan');
const data = { hi: 596, hello: { a: [] }, h: { a: '' }, hey: { a: 293, b: '23', c: { 1: [] }, d: [{ 1: { z: [] }, 2: '123' }] }, hola: 123 };
const prune = objectScan(['**.*.*'], {
rtn: 'count',
filterFn: ({ gparent, gproperty, value }) => {
if (Array.isArray(value) && value.length === 0) {
delete gparent[gproperty];
return true;
}
return false;
}
});
console.log(prune(data));
// => 3
console.log(data);
// => { hi: 596, h: { a: '' }, hey: { a: 293, b: '23', d: [ { '2': '123' } ] }, hola: 123 }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#15.0.0"></script>
Disclaimer: I'm the author of object-scan
In other words, how to turn original into expected, or how to set the values of keys which values are empty strings with a dot notation formatted representation of the key's "path" inside the object ?
original = {
A: {
B: {
C: {
D: "",
E: ""
},
F: {
G: "",
H: ""
}
}
}
}
expected = {
A: {
B: {
C: {
D: "A.B.C.D",
E: "A.B.C.E
},
F: {
G: "A.B.F.G",
H: "A.B.F.H"
}
}
}
}
Surely people must have stumbled on this kind of issue when dealing with "stringly-typed" systems ?
It can be done in that way:
const original = {
A: {
B: {
C: {
D: "",
E: ""
},
F: {
G: "",
H: ""
}
}
}
}
const addPath = (data, path = []) => {
return Object.keys(data).reduce((acc, key) => {
const currentPath = [...path, key]
return {
...acc,
[key]: typeof data[key] === 'object'
? addPath(data[key], currentPath)
: currentPath.join('.')
}
}, {})
}
console.log(addPath(original))
I'm trying to implement multiple sorting on nested array based on keys and sort order.
I'm able to sort an array based on the key and order
I've done the following code for sorting on array,
return array.sort((a, b) => {
let i = 0, result = 0;
while (i < sortBy.length && result === 0) {
if (typeof a[sortBy[i].prop] == "string") {
result = sortBy[i].direction * (a[sortBy[i].prop].toString() < b[sortBy[i].prop].toString() ? -1 : (a[sortBy[i].prop].toString() > b[sortBy[i].prop].toString() ? 1 : 0));
} else {
result = sortBy[i].direction * (a[sortBy[i].prop] < b[sortBy[i].prop] ? -1 : (a[sortBy[i].prop] > b[sortBy[i].prop] ? 1 : 0));
}
i++;
}
return result;
});
My Input Data is given below,
array = [{ x: [{ d: 4 }, { d: 2 }, { d: 3 }, { d: 1 }], b: 'v' }, { x: [{ d: 8 }, { d: 7 }, { d: 5 }, { d: 6 }], b: 's' }];
sortBy= [{ 'prop': 'b', 'direction': 1 }, { 'prop': 'd', 'direction': 1}];
I expected an output like below,
array = [{ x: [{ d: 5 }, { d: 6 }, { d: 7 }, { d: 8 }], b: 's' },{ x: [{ d: 1 }, { d: 2 }, { d: 3 }, { d: 4 }], b: 'v' }];
But I'm getting the below result,
array = [{ x: [{ d: 8 }, { d: 7 }, { d: 5 }, { d: 6 }], b: 's' },{ x: [{ d: 4 }, { d: 2 }, { d: 3 }, { d: 1 }], b: 'v' }];
I'm stuck on how to solve this logical problem. can anyone help me on this.
You're close, what you will want to do is call your sort method on the inner arrays that have the property x. Then afterwords call it on the original array. That will sort the subarrays with the d property first, then afterword sort outer array by b.
array = [{ x: [{ d: 4 }, { d: 2 }, { d: 3 }, { d: 1 }], b: 'v' }, { x: [{ d: 8 }, { d: 7 }, { d: 5 }, { d: 6 }], b: 's' }];
sortBy= [{ 'prop': 'b', 'direction': 1 }, { 'prop': 'd', 'direction': 1}];
function sortFunc(a, b) {
let i = 0, result = 0;
while (i < sortBy.length && result === 0) {
if (typeof a[sortBy[i].prop] == "string") {
result = sortBy[i].direction * (a[sortBy[i].prop].toString() < b[sortBy[i].prop].toString() ? -1 : (a[sortBy[i].prop].toString() > b[sortBy[i].prop].toString() ? 1 : 0));
} else {
result = sortBy[i].direction * (a[sortBy[i].prop] < b[sortBy[i].prop] ? -1 : (a[sortBy[i].prop] > b[sortBy[i].prop] ? 1 : 0));
}
i++;
}
return result;
}
array.forEach(arr => arr.x = arr.x.sort(sortFunc));
array = array.sort(sortFunc);
console.log(array);
From what I see you need to make this one recursive and call the function whenever you hit an array in your object. Take a look at this
let input = [
{ x: [{ d: 4 }, { d: 2 }, { d: 3 }, { d: 1 }], b: 'v' },
{ x: [{ d: 8 }, { d: 7 }, { d: 5 }, { d: 6 }], b: 's' }
];
const sortingInstructions = [
{prop: 'b', direction: 1},
{prop: 'd', direction: 1}
];
const isArray = Obj => {
return typeof(Obj) === 'object' && Obj.length !== undefined
}
const nestedSort = SortingArray => {
for(let obj of SortingArray){
for(let key of Object.keys(obj)){
if(isArray(obj[key])){
nestedSort(obj[key])
}
}
}
for(let instruction of sortingInstructions){
SortingArray.sort((a,b) => {
if(typeof(a[instruction.prop]) === 'string'){
return instruction.direction*b[instruction.prop].localeCompare(a[instruction.prop])
}
if(typeof(a[instruction.prop]) === 'number'){
return instruction.direction*(b[instruction.prop]-a[instruction.prop]);
}
})
}
return SortingArray;
}
nestedSort(input);
console.log(input);
What this does is the following.
if you hit an array in your object you call the function recursively on this array
you sort the array by all your instructions (the ones further down in your instructions beeing the dominant ones as they are used to sort the latest)
if what you are hitting is a string you use localeCompare to sort it
As this is recursive it will also run on any depth of object. So e.g.
let input = [
{ x: [{y:[{ d: 4 }, { d: 2 }, { d: 3 }, { d: 1 }]}], b: 'v' },
{ x: [{z:[{ d: 8 }, { d: 7 }, { d: 5 }, { d: 6 }]}], b: 's' }
]
would also work
I have the following code and test data:
const getNestedObject = (nestedObj, pathArr) => {
return pathArr.reduce((obj, key) => {
return (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj;
});
}
const obj =
[
{
a: 1,
c: [
{
d: 1,
e: 'string',
f: [
{
value: 0,
},
{
value: 1,
}
],
},
],
},
{
a: 2,
c: [
{
d: 2,
e: 'string',
f: [
{
value: 3,
},
{
value: 4,
}
],
},
],
},
];
console.log(obj);
const fs = obj.map(o => getNestedObject(o, ['c', 'f']));
console.log(fs);
What I want to do is given the array of objects shown below, I want to get only the property called f from every object in the array. So, basically end result should be array of f values of every object. Since 'f' is an array, I would highly appreciate the end result to be just one array with elements from all 'f' properties, so kind of every of these 'f' to be spread out, so I have one array. My above getNestedObject function does not seem to work, as when the console.log statement below returns the whole object. Any ideas how to do this in JS?
So basically the end result should be:
[{ value: 0 }, { value: 1 }, { value: 3 }, {value: 4 }]
You can combine reduce() with map(). Basically reduce your main array into an flattened array of all the c.f items. This checks for the c property just in case the object doesn't have it:
const obj = [{a: 1,c: [{d: 1,e: 'string',f: [{value: 0,},{value: 1,}],},],},{a: 2,c: [{d: 2,e: 'string',f: [{value: 3,},{value: 4,}],},],},];
let Fs = obj.reduce((arr, item) =>
item.c
? arr.concat(...item.c.map(itemc => itemc.f )) // concat for flattened rather than nested arrays
: arr
, []);
console.log(Fs)
Here's a fast iterative solution that won't overflow the stack, makes no assumptions about target result values being arrays (only spreads if they are) and doesn't hard-code child key names (it'll explore any values that are arrays).
This can also work if the target has children matching the key that you'd like to include in the search (swap else if with if).
const get = (data, target) => {
const result = [];
const stack = [data];
while (stack.length) {
const curr = stack.pop();
for (const o of curr) {
for (const k in o) {
if (k === target) {
if (Array.isArray(o[k])) {
result.push(...o[k]);
}
else {
result.push(o[k]);
}
}
else if (Array.isArray(o[k])) {
stack.push(o[k]);
}
}
}
}
return result;
};
const obj =
[
{
a: 1,
c: [
{
d: 1,
e: 'string',
f: [
{
value: 0,
},
{
value: 1,
}
],
},
],
},
{
a: 2,
c: [
{
d: 2,
e: 'string',
f: [
{
value: 3,
},
{
value: 4,
}
],
},
],
},
];
console.log(get(obj, "f"));
You can recursively traverse any objects and arrays to fetch a given property. This works at any depth and doesn't care about the structure of the objects:
const obj=[{a:1,c:[{d:1,e:"string",f:[{value:0},{value:1}]}]},{a:2,c:[{d:2,e:"string",f:[{value:3},{value:4}]}]}];
//curried function to grab a property by name off some object or array
function grab(prop) {
//naming the inner function means it can be called recursively by name
return function recursiveGrab(target) {
if (Array.isArray(target)) {
const arrayResult = target
.filter(x => typeof x === "object") //remove non-objects (and non-arrays)
.filter(Boolean) //remove null
.map(recursiveGrab); //recursively call for the remaining objects
return flatten(arrayResult); //return a single dimensional array
}
//an object has the property - return it
if (prop in target) {
return target[prop];
}
//object doesn't have the property - check all values
return recursiveGrab(Object.values(target));
}
}
//small helper function. It's separated only to keep the logic for the traversal clear
function flatten(arr) {
return arr.reduce((acc, curr) => acc.concat(curr), [])
}
const grabF = grab('f');
console.log(grabF(obj));
I did not notice that f was always inside c. I have this recursive and dirty looking solution that works with f being inside any of the fields
const objArray = [
{
a: 1,
c: [
{
d: 1,
e: 'string',
f: [
{
value: 0,
},
{
value: 1,
}
],
},
],
d: [
{
d: 1,
e: 'string',
f: [
{
value: 'd',
},
{
value: 'd1',
}
],
},
],
},
{
a: 2,
c: [
{
d: 2,
e: 'string',
f: [
{
value: 3,
},
{
value: 4,
}
],
},
],
e: [
{
d: 1,
e: 'string',
f: [
{
value: 'e',
},
{
value: 'e1',
}
],
},
],
}
]
const getFObject = (obj) => {
let fObj = [];
Object.keys(obj).some(key => {
if (key === 'f') {
fObj = obj[key];
return true;
}
if (Array.isArray(obj[key])) {
obj[key].forEach(nestedObj => {
fObj = fObj.concat(getFObject(nestedObj))
});
}
return false;
});
return fObj;
}
const newArray = objArray.reduce((acc, obj) => {
return acc.concat(getFObject(obj))
}, []);
console.log(newArray)