Reformatting array of arrays to nested json in Javascript [duplicate] - javascript

I have an array like
[
"parent1|child1|subChild1",
"parent1|child1|subChild2",
"parent|child2|subChild1",
"parent1|child2|subChild2",
"parent2|child1|subChild1",
"parent2|child1|subChild2",
"parent2|child2|subChild1",
.
.
.
]
Wherein my first string before | is the parent and the second string before | is the child and the third string after the second | is the subchild
How can I convert this array into an object like
[
{
"id": "parent1",
"children":[
{
"id": "child1",
"children":[
{
"id": "subChild1"
}
]
}
]
}
]
Parent -> child -> subchild object
Based on Sebastian's answer I tried below using typescript
private genTree(row) {
let self = this;
if (!row) {
return;
}
const [parent, ...children] = row.split('|');
if (!children || children.length === 0) {
return [{
id: parent,
children: []
}];
}
return [{
id: parent,
children: self.genTree(children.join('|'))
}];
}
private mergeDeep(children) {
let self = this;
const res = children.reduce((result, curr) => {
const entry = curr;
const existing = result.find((e) => e.id === entry.id);
if (existing) {
existing.children = [].concat(existing.children, entry.children);
} else {
result.push(entry);
}
return result;
}, []);
for (let i = 0; i < res.length; i++) {
const entry = res[i];
if (entry.children && entry.children.length > 0) {
entry.children = self.mergeDeep(entry.children);
}
};
return res;
}
private constructTree(statKeyNames){
let self = this;
const res = this.mergeDeep(statKeyNames.map(self.genTree).map(([e]) => e));
console.log(res);
}
but this gives me:
Cannot read property 'genTree' of undefined" error
Update:
As per Sebastian's comment changed self.genTree to this.genTree.bind(this) and it worked without any issues

You could use a mapper object which maps each object to it's unique path (You could map the object with each id, but id is not unique here). Then reduce each partial item in the array. Set the root object as the initialValue. The accumulator will be the parent object for the current item. Return the current object in each iteration.
const input = [
"parent1|child1|subChild1",
"parent1|child1|subChild2",
"parent1|child2|subChild1",
"parent1|child2|subChild2",
"parent2|child1|subChild1",
"parent2|child1|subChild2",
"parent2|child2|subChild1"
],
mapper = {},
root = { children: [] }
for (const str of input) {
let splits = str.split('|'),
path = '';
splits.reduce((parent, id, i) => {
path += `${id}|`;
if (!mapper[path]) {
const o = { id };
mapper[path] = o; // set the new object with unique path
parent.children = parent.children || [];
parent.children.push(o)
}
return mapper[path];
}, root)
}
console.log(root.children)

You have to use recursion for that. Take a look here:
const arr = [
"parent1|child1|subChild1",
"parent1|child1|subChild2",
"parent|child2|subChild1",
"parent1|child2|subChild2",
"parent2|child1|subChild1",
"parent2|child1|subChild2",
"parent2|child2|subChild1"
];
function genTree(row) {
const [parent, ...children] = row.split('|');
if (!children || children.length === 0) {
return [{
id: parent,
children: []
}];
}
return [{
id: parent,
children: genTree(children.join('|'))
}];
};
function mergeDeep(children) {
const res = children.reduce((result, curr) => {
const entry = curr;
const existing = result.find((e) => e.id === entry.id);
if (existing) {
existing.children = [].concat(existing.children, entry.children);
} else {
result.push(entry);
}
return result;
}, []);
for (let i = 0; i < res.length; i++) {
const entry = res[i];
if (entry.children && entry.children.length > 0) {
entry.children = mergeDeep(entry.children);
}
};
return res;
}
const res = mergeDeep(arr.map(genTree).map(([e]) => e));
console.log(JSON.stringify(res, false, 2));
I used two helpers here: genTree(row) which recursively generates a simple tree from each row, and mergeDeep(children) which reduces the first-level trees in the result of arr.map(genTree).map(([e]) => e), and then iterates over the array and recursively does the same thing to all children of each entry.

Related

JavaScript: Create object with nested properties from string split by specific character

How to use the name property in this object:
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
To create an object with the following format:
{
root: {
branch: {
subbranch: {
leaf: 'my-value'
}
}
}
}
You could do this using split and reduce
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
let newObj = {}
const parts = obj.name.split('/')
parts.reduce((prev, curr, i) => (
Object.assign(
prev,
{[curr]: i === parts.length - 1 ? obj.value : Object(prev[curr])}
),
prev[curr]
), newObj)
console.log(newObj)
I wrote a reciursive function and a wrapper to call it.
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
const recursiveNest = (result, value, arr, index = 0) => {
const path = arr[index]
if (index < arr.length - 1) {
result[path] = {}
index +=1;
recursiveNest(result[path], value, arr, index)
} else {
result[arr[index]] = value;
}
};
const createNestedObject = (obj, splitBy) => {
let result = {}
recursiveNest(result, obj.value, obj.name.split(splitBy))
return result;
}
console.log(createNestedObject(obj, '/'));
Lodash provides setWith(object, path, value, [customizer]):
const obj = {
name: 'root/branch/subbranch/leaf',
value: 'my-value'
}
console.log(_.setWith({}, obj.name.split('/'), obj.value, _.stubObject))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>

I want to turn a JavaScript array into a nested object form

I want to convert an array with this shape into a nested object shape.
I've been thinking about it for hours, but I can't come up with a proper way. Any good way?
before
let files2 = [
"folder/empty_file.txt",
"folder/inner file.txt",
"user16.txt",
"reagjjjd.md",
"folder/folder2/hi.txt",
];
after
let files = [
{ folder: [{ folder2: ["hihi.txt"] }, "empty_file.txt", "inner file.txt"] },
"user16.txt",
"reagjjjd.md",
];
I would appreciate any help or guidance here. Thanks!
Here is a solution that can convert arbitrary folder depth into a tree according to your requirement. It basically builds the tree through text splitting and recursion, which works but is likely not ideal. I wish it were a little simpler, but hopefully it helps if no-one else offers a better solution.
// const path = require("path");
// const sep = path.sep;
const sep = "/";
const input = [
"folder/empty_file.txt",
"folder/inner file.txt",
"user16.txt",
"reagjjjd.md",
"folder/folder2/hi.txt",
];
const get_files = (list) => {
return list.filter((s) => s.length < 2).map((s) => s[0]);
};
const get_folder_names = (list) => {
return [...new Set(list.filter((s) => s.length > 1).map((s) => s[0]))];
};
const get_folder_contents = (list, folder) => {
return list
.filter((s) => s.length > 1)
.filter((s) => s[0] == folder)
.map((s) => s.slice(1));
};
const get_folders = (list) => {
return get_folder_names(list).map((folder) => {
return { [folder]: build_tree(get_folder_contents(list, folder)) };
});
};
function build_tree(list) {
const tree = [];
tree.push(...get_files(list));
tree.push(...get_folders(list));
return tree;
}
const list = input.map((s) => s.split(sep));
const output = build_tree(list);
console.log("Tree:", JSON.stringify(output));
newArr by your structure .
newArr1 by my structure suggest
let files = [
"folder/empty_file.txt",
"folder/inner file.txt",
"user16.txt",
"reagjjjd.md",
"folder/folder2/hi.txt",
]
let newArr = files.reduce((res, path) => {
let convertArr = path.split("/")
if(convertArr.length < 2) return [...res, path]
let parent = res
let treePath = convertArr.forEach( (ele, key) => {
let temParent = parent.find(el => typeof el === 'object' && el.hasOwnProperty(ele))
if (temParent) {
parent = temParent[ele]
} else {
let tmp = key === convertArr.length-1 ? ele : {[ele] : []}
parent.push(tmp)
parent = tmp[ele]
}
})
return res
}, [])
let files2 = [
"folder/empty_file.txt",
"folder/inner file.txt",
"user16.txt",
"reagjjjd.md",
"folder/folder2/hi.txt",
]
let newArr1 = files2.reduce((res, path) => {
let convertArr = path.split("/")
let parent = res
let treePath = convertArr.forEach( ele => {
let temParent = parent.find(el => el.path === ele)
if (!temParent) {
let tmp = {path: ele, children: []}
parent.push(tmp)
parent = tmp.children
} else {
parent = temParent.children
}
})
return res
}, [])
console.log(newArr)
console.log(newArr1)
I think the array structure should follow this format for ease of use :
[
{
path : 'folder',
children : [
{
path : 'empty_file.txt',
children : []
},
{
path : 'inner file.txt',
children : []
},
{
path : 'folder2',
children : [
{
path : 'hi.txt',
children : []
}
]
},
]
},
{
path : 'user16.txt',
children : []
},
{
path : 'reagjjjd.md',
children : []
}
]

Node.js - How to merge objects inside an array based on condition?

In Node.js, I have 3 sets of data like
[
{
"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
"dailyData":159392.235451,
"dailyDataInUSC":255.284807
}
]
and
[
{
"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
"monthlyData":159392.235451,
"monthlyDataInUSC":255.284807
},
{
"userId":"23fs6fds3-34k4-17de-3123-d2ec81e8aaf3",
"monthlyData":349392.455451,
"monthlyDataInUSC":655.234807
}
]
and
[
{
"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
"threeMonthsData":159392.235451,
"threeMonthsDataInUSC":255.284807
},
{
"userId":"23fs6fds3-34k4-17de-3123-d2ec81e8aaf3",
"threeMonthsData":349392.455451,
"threeMonthsDataInUSC":655.234807
},
{
"userId":"34sdf34-67j4-54nd-6763-d2ec81e8aaf3",
"threeMonthsData":6789392.455451,
"threeMonthsDataInUSC":905.655807
}
]
How can I combine this to one object based on userId(filter) inside an array.
Eg, output should be like
[
{
"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
"dailyData":159392.235451,
"dailyDataInUSC":255.284807,
"monthlyData":159392.235451,
"monthlyDataInUSC":255.284807,
"threeMonthsData":159392.235451,
"threeMonthsDataInUSC":255.284807
}
]
Please help me to achieve this.
A combination of spread, reduce and findIndex can be used to solve the problem.
Combine the original arrays into a single array using the spread operator.
Use reduce to group the elements by key (in this case userId)
Something like this :
const dailyData = [{"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3","dailyData":159392.235451,"dailyDataInUSC":255.284807}];
const monthlyData = [{"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3","monthlyData":159392.235451,"monthlyDataInUSC":255.284807}, {"userId":"23fs6fds3-34k4-17de-3123-d2ec81e8aaf3","monthlyData":349392.455451,"monthlyDataInUSC":655.234807}]
const triMonthlyData = [{"userId":"54c7f3ef-64d4-40de-8100-d2ec81e8aaf3","threeMonthsData":159392.235451,"threeMonthsDataInUSC":255.284807}, {"userId":"23fs6fds3-34k4-17de-3123-d2ec81e8aaf3","threeMonthsData":349392.455451,"threeMonthsDataInUSC":655.234807}, {"userId":"34sdf34-67j4-54nd-6763-d2ec81e8aaf3","threeMonthsData":6789392.455451,"threeMonthsDataInUSC":905.655807}]
const combinedData = [...dailyData, ...monthlyData, ...triMonthlyData].reduce((mergedResult, curElement) => {
let matchingElementIdx = mergedResult.findIndex(ele => ele.userId === curElement.userId);
if (matchingElementIdx !== -1) {
mergedResult[matchingElementIdx] = {...mergedResult[matchingElementIdx], ...curElement};
} else {
mergedResult = [...mergedResult, curElement];
}
return mergedResult;
}, []);
console.log(combinedData);
const aa = () => {
let aa = [
{
userId: "54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
dailyData: 159392.235451,
dailyDataInUSC: 255.284807
}
];
let bb = [
{
userId: "54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
monthlyData: 159392.235451,
monthlyDataInUSC: 255.284807
},
{
userId: "23fs6fds3-34k4-17de-3123-d2ec81e8aaf3",
monthlyData: 349392.455451,
monthlyDataInUSC: 655.234807
}
];
let cc = [
{
userId: "54c7f3ef-64d4-40de-8100-d2ec81e8aaf3",
threeMonthsData: 159392.235451,
threeMonthsDataInUSC: 255.284807
},
{
userId: "23fs6fds3-34k4-17de-3123-d2ec81e8aaf3",
threeMonthsData: 349392.455451,
threeMonthsDataInUSC: 655.234807
},
{
userId: "34sdf34-67j4-54nd-6763-d2ec81e8aaf3",
threeMonthsData: 6789392.455451,
threeMonthsDataInUSC: 905.655807
}
];
let newArrObj = aa;
bb.forEach(item => {
let index = newArrObj.findIndex(item1 => item1.userId === item.userId);
if (index === -1) {
newArrObj = [...newArrObj, item];
} else {
newArrObj[index] = { ...newArrObj[index], ...item };
}
});
cc.forEach(item => {
let index = newArrObj.findIndex(item1 => item1.userId === item.userId);
if (index === -1) {
newArrObj = [...newArrObj, item];
} else {
newArrObj[index] = { ...newArrObj[index], ...item };
}
});
console.log(newArrObj);
};

Better way to map a deep object to new object

This code works for converting the JSON to an object where each name object turns into the key for either its value, or if it instead has its own element object breaks that out and does the same to its contents.
Is there a better way to do this that would also allow for more extensiblity of the JSON schema?
Is there a way I can get it all down to a simpler function that I can pass the first element and have it convert it down to whatever depth the schema goes?
const fs = require('fs');
{
let scheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version='1.0'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root;
let depth = 0;
var compiled = {
[scheme.ele.name]: scheme.ele.ele.map(function(i) {
if (typeof i.ele != 'undefined') {
return {
[i.name]: i.ele.map(function(k) {
if (typeof k.ele != 'undefined') {
return {
[k.name]: k.ele.map(function(p) {
if (typeof p.ele != 'undefined') {
return {
[p.name]: p.ele
};
} else {
return {
[p.name]: p.value
};
}
})
};
} else {
return {
[k.name]: k.value
};
}
})
};
} else {
return {
[i.name]: i.value
};
}
})
};
}
console.log(JSON.stringify(compiled, 0, 2));
I should add, this is intended to eventually also apply validation and grab real data when it gets to the string objects.
The output looks like this:
{
"REPORT": [
{
"SEGMENT0": [
{
"NUMBER1": ""
},
{
"NUMBER2": ""
}
]
},
{
"SEGMENT1": [
{
"RECORD1": [
{
"NUMBER1": ""
},
{
"NUMBER2": ""
}
]
}
]
},
{
"SEGMENT2": []
},
{
"SEGMENT3": []
},
{
"SEGMENT4": []
},
{
"SEGMENT5": []
}
]
}
You could destructure the object, get name, ele and value and return a new object with name as key and either an array by mapping the objects of ele or the value.
const
getData = ({ name, ele, value }) => ({
[name]: Array.isArray(ele)
? ele.map(getData)
: value
});
var scheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version=\'1.0\'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root,
result = getData(scheme.ele);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina's answer is cleaner but this looks a bit more like your code so I figured I'd post it anyway.
let scheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version=\'1.0 \'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":""},{"name":"NUMBER2","value":"1"}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":"2"},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root;
let newScheme = JSON.parse('{"$schema":{"root":{"name":"THINGY","dtd":{"name":"DOCTYPE","value":"something.dtd","commentBefore":["?xml version=\'1.0 \'?","Version NULL"]},"ele":{"name":"REPORT","ele":[{"name":"SEGMENT0","ele":[{"name":"NUMBER1","value":"1"},{"name":"NUMBER2","value":"3"}]},{"name":"SEGMENT1","ele":[{"name":"RECORD1","ele":[{"name":"NUMBER1","value":"4"},{"name":"NUMBER2","value":""}]}]},{"name":"SEGMENT2","ele":[]},{"name":"SEGMENT3","ele":[]},{"name":"SEGMENT4","ele":[]},{"name":"SEGMENT5","ele":[]}]}}}}').$schema.root;
//Yay, recursion!
function mapObj(a, o = {}) {
let array = o[a.name] || [];
for (let i = 0; i < a.ele.length; i++) {
let b = a.ele[i];
array[i] = b.ele ?
mapObj(b, array[i]) : {
[b.name]: b.value
};
}
o[a.name] = array;
return o;
}
let obj = mapObj(scheme.ele);
console.log(obj);
console.log(mapObj(newScheme.ele, obj));

Transform an array to an object with nested properties according to the array

I have several arrays as the following:
[ 'businessOpenAccount', 'accountSettings1.page.js' ]
[ 'businessOpenAccount', 'accountSettings2.page.js' ]
[ 'mainTest', 'test', 'test1.page.js' ]
[ 'mainTest', 'test', 'test2.page.js' ]
My expected result is to have an object in this way:
{
businessOpenAccount: {
'accountSettings1.page.js': {},
'accountSettings2.page.js': {}
},
mainTest: {
test: {
'test1.page.js': {},
'test2.page.js': {}
}
}
}
So actually I want to parse the arrays and build a nested object to return from them, but being sure to check that if a property already exists (because defined from a previous array) I won't override it, but just add the new nested property in it, respecting the correct order of nesting.
I tried few approaches using reduce, reduceRight and simple forEach/for loops but I still cannot really achieve the solution I would like.
Any tips please?
This is the best way I have so far, but I override the properties cycling over multiple arrays (the example with a single array):
const relevantFilePath = ['businessOpenAccount', 'accountSettings.page.js'];
let obj = {};
relevantFilePath.forEach((el, ind) => {
if (ind === 0) {
obj[el] = {};
previousEl = obj[el];
} else {
previousEl[el] = {};
previousEl = previousEl[el];
}
});
console.log(obj);
You could iterate the given data and reduce the object and take the last item with splitted value.
var data = [['businessOpenAccount', 'accountSettings1.page.js'], ['businessOpenAccount', 'accountSettings2.page.js'], ['mainTest', 'test', 'test1.page.js'], ['mainTest', 'test', 'test2.page.js']],
object = {};
data.forEach(function (a) {
var last = a.pop().split('.')[0];
a.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = '';
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6 with without mutating original data
var data = [['businessOpenAccount', 'accountSettings1.page.js'], ['businessOpenAccount', 'accountSettings2.page.js'], ['mainTest', 'test', 'test1.page.js'], ['mainTest', 'test', 'test2.page.js']],
object = {};
data.forEach(function (a) {
var temp = a.slice(),
last = temp.pop().split('.')[0];
temp.reduce((o, k) => o[k] = o[k] || {}, object)[last] = '';
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A recursive solution which would work for anykind of sub array with unlimited depth.
const a = [
['businessOpenAccount', 'accountSettings1.page.js'],
['businessOpenAccount', 'accountSettings2.page.js'],
[ 'businessOpenAccount', 'test1',
[
['test2', 'test2.settings.page.js',
[
['test2', 'test2.settings.page.js'],
['test3', 'test3.settings.page.js'],
],
'test4',
],
['test3', 'test3.settings.page.js'],
]
],
['mainTest', 'test', 'test1.page.js'],
['mainTest', 'test', 'test2.page.js'],
];
const result = {};
const traverse = (result, arr) => {
let firstEl = arr.shift();
if (firstEl instanceof Array) {
if (arr.length >= 1) {
traverseTop(result, firstEl);
return traverse(result, arr);
}
return traverseTop(result, firstEl);
}
firstEl = firstEl.split('.')[0];
result[firstEl] = arr.length >= 1 ? (result[firstEl] || {}) : '';
if (arr.length >= 1) {
return traverse(result[firstEl], arr);
}
return;
};
const traverseTop = (result, arr) => {
arr.forEach((subArr) => {
const firstEl = subArr.shift().split('.')[0];
result[firstEl] = result[firstEl] || {};
traverse(result[firstEl], subArr)
});
return result;
};
console.log(traverseTop(result, a));

Categories

Resources