Convert flat structure with any number of levels array into tree structure - javascript

I want to convert flat structure array into a tree structure for one my projects. Below is the input and expected output:
Input:
let input=[
{
lvl1:"Code1",
lvl2:"Type1",
lvl3:"Desc1",
lvl4:"Check1"
},
{
lvl1:"Code1",
lvl2:"Type1",
lvl3:"Desc1",
lvl4:"Check2"
},
{
lvl1:"Code2",
lvl2:"Type2",
lvl3:"Desc2",
lvl4:"Check1"
},
]
Output:
[
{
level_key:"lvl1",
level_value:"Code1",
children:[
{
level_key:"lvl2",
level_value:"Type1",
children:[
{
level_key:"lvl3",
level_value:"Desc1",
children:[
{
level_key:"lvl4",
level_value:"Check1",
children:[]
},
{
level_key:"lvl4",
level_value:"Check2",
children:[]
}
]
}
]
}
]
},
{
level_key:"lvl1",
level_value:"Code2",
children:[
{
level_key:"lvl2",
level_value:"Type2",
children:[
{
level_key:"lvl3",
level_value:"Desc2",
children:[
{
level_key:"lvl4",
level_value:"Check1",
children:[]
}
]
}
]
}
]
}
]
Here in eg. i have taken till lvl4 but any number of levels could be there like lvl5, lvl6....
I have tried a approach but i feel that is very complex and not scalable.

You could take obbjects with level_value as key and take the arrays as result.
const
flat = [{ lvl1:"Code1", lvl2:"Type1", lvl3:"Desc1", lvl4:"Check1" }, { lvl1:"Code1", lvl2:"Type1", lvl3:"Desc1", lvl4:"Check2" }, { lvl1:"Code2", lvl2:"Type2", lvl3:"Desc2", lvl4:"Check1" }],
tree = flat.reduce((r, o) => {
let temp = r,
i = 1,
level_key = `lvl${i}`,
level_value = o[level_key];
do {
if (!temp[level_value]) {
temp[level_value] = { _: [] };
temp._.push({ level_key, level_value, children: temp[level_value]._ });
}
temp = temp[level_value];
level_key = `lvl${++i}`;
level_value = o[level_key];
} while (level_value)
return r;
}, { _: [] })._;
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

convert a flat array that has a "path" property to a nested array

I have a flat array like this example :
[
{
'name':'itemA',
'path':'foo/bar'
},
{
'name':'itemB',
'path':'bar/foo'
},
{
'name':'itemC',
'path':'foo'
},
{
'name':'itemD',
'path':'bar'
},
{
'name':'itemE',
'path':'foo/bar/wizz'
},
{
'name':'itemF',
'path':'bar/foo'
},
]
I want to build a tree based on the "path" property, so I could get this output :
[
{
'name':'itemD',
'path':'bar',
'items':[
{
'name':'itemD',
'path':'bar/foo'
},
{
'name':'itemF',
'path':'bar/foo'
}
]
},
{
'name':'itemC',
'path':'foo',
'items':[
{
'name':'itemA',
'path':'foo/bar',
'items':
[
{
'name':'itemE',
'path':'foo/bar/wizz'
}
]
},
]
}
]
How could I achieve that ?
I found out some examples like this one, but they are based on a parent ID and not a "path" like mine.
Thanks a lot !
You could find the level or add a new object for the level of the splitted path.
const
data = [{ name: 'itemA', path: 'foo/bar' }, { name: 'itemB', path: 'bar/foo' }, { name: 'itemC', path: 'foo' }, { name: 'itemD', path: 'bar' }, { name: 'itemE', path: 'foo/bar/wizz' }, { name: 'itemF', path: 'bar/foo' }],
tree = data.reduce((items, { name, path }) => {
path.split('/').reduce((o, _, i, p) => {
let path = p.slice(0, i + 1).join('/'),
temp = (o.items ??= []).find(q => q.path === path);
if (!temp) o.items.push(temp = { name, path });
return temp;
}, { items });
return items;
}, []);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Transforming an array into an array of arrays

I am trying to do an opposite of flattening an array.
I have the following input JSON array of 4 elements:
[
{
"nestedObj": {
"id":12
}
},
{
"nestedObj": {
"id":555
}
},
{
"nestedObj": {
"id":555
}
},
{
"nestedObj" :{
"id":771
}
}
]
I want to transform it to an array of arrays, where each subarray has elements of the same nestedObj.id grouped up together.
I can assume the initial JSON is sorted by nestedObj.id.
In the above example, the id of nestedObj of 2nd and 3rd element are the same (555), so those elements would be grouped into one sub-array.
This would be the result, an array of only 3 sub-array elements:
[
[{
"nestedObj": {
"id":12
}
}],
[{
"nestedObj": {
"id":555
}
},
{
"nestedObj": {
"id":555
}
}],
[{
"nestedObj" :{
"id":771
}
}]
]
And this is the code that gets me what I want:
const data = [ /* ...the above input data... */ ];
let result = [];
let prevId = null;
for (let elem of data) {
let currId = elem.nestedObj.id;
if (currId === prevId) {
result[result.length - 1].push({...elem});
} else {
result.push([{...elem}]);
}
prevId = currId;
}
But as you can see... the code is very declarative. It's not very JavaScript-like, in a functional programming sense.
How can I re-write it using e.g. reduce or other 'modern JS' techniques?
Just group the objects.
let array = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }],
result = Object.values(array.reduce((r, o) => {
(r[o.nestedObj.id] = r[o.nestedObj.id] || []).push(o);
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can group by id using the function reduce, extract the grouped values using the function Object.values, and finally map the array to build the desired output.
This is assuming we have only one attribute called nestedObj
let arr = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }],
result = Object.values(arr.reduce((a, {nestedObj: {id}}) => {
(a[id] || (a[id] = [])).push(id);
return a;
}, {})).map(r => r.map(id => ({nestedObj: {id}})));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use a Map to group the items with same id then get the final values from the Map
const data = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }]
const map = new Map;
data.forEach(o => {
const {nestedObj:{id}} = o;
map.has(id) ? map.get(id).push(o) : map.set(id,[o]);
});
console.log([...map.values()])

How to create a nested object from the list of object by property value

I faced the real problem with parsing API and combining the response into the proper object. I've tried many solutions, but they are all failed in many ways.
If there is an answer with a solution for such question - please provide a link. Because I found many questions about how to nest, but no one how to work with shifting.
So far I have a response which looks like this:
[
{path: "1.0.0"},
{path: "1.1"},
{path: "13"},
{path: "2"},
{path: "1"},
{path: "2.0.0.1"},
{path: "3.0.1.1"}
]
and I need to transform into this:
[
{
path:"1",
subCategories:[
{
path:"1.0.0",
subcategories:[]
},
{
path:"1.1",
subcategories:[
{
path:"1.1.0",
subcategories: []
}
]
},
]
},
{
path:"13",
subcategories:[]
},
{
path:"2",
subcategories:[
{
path:"2.0.0.1",
subcategories:[]
}
]
},
{
path:"3.0.1.1",
subcategories:[]
},
]
The main complexity is that on server side we can hide any category and in this case child should take parent's place. Parent and child separated with a dot.
I would be very appreciated if you help me. Thank you.
You could take a recursive approach with a two step division into collecting same level items and then creating objects.
function getTree(array, prefix = []) {
function getCommon(array) {
return array.reduce((a, b) => {
var l = 0;
while (a[l] === b[l] && l in a) l++;
return a.slice(0, l);
});
}
var l = array.length && getCommon(array) + 1;
return array
.reduce((r, a) => {
var left = a.slice(0, l),
right = a.slice(l),
temp = r.find(([b]) => left.join('.') === b.slice(0, l).join('.'));
if (temp) {
temp[1].push(right);
} else {
r.push([left, [right]]);
}
return r;
}, [])
.map(([p, sub]) => {
var path = prefix.concat(p, sub.length === 1 ? sub[0] : []);
return {
path: path.join('.'),
subcategories: sub.length === 1
? []
: getTree(sub.filter(({ length }) => length), path)
};
});
}
var data = [{ path: "1.0.0" }, { path: "1.1" }, { path: "1.1.0" }, { path: "13" }, { path: "2" }, { path: "1" }, { path: "2.0.0.1" }, { path: "3.0.1.1" }],
tree = getTree(data.map(({ path }) => path.split('.')));
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This only support 2 levels, if you need to support any number of nesting you'll need a recursive solution
var o = [{
path: "1.0.0"
},
{
path: "1.1"
},
{
path: "13"
},
{
path: "2"
},
{
path: "1"
},
{
path: "1.1.0"
},
{
path: "2.0.0.1"
},
{
path: "3.0.1.1"
}
]
const result = o.reduce((memo, d) => {
const key = d.path.split('.')[0]
memo[key] ? memo[key].push(d) : memo[key] = [d]
return memo
}, [])
.filter(Boolean)
.map(items => items.map(d => d.path).sort())
.map(items => items.reduce((memo, d) => {
const dTransposed = {
path: d,
subCategories: []
}
if (!memo.path) {
return dTransposed
}
const parent = memo.subCategories.find(c => d.startsWith(c.path))
if (parent) {
parent.subCategories.push(dTransposed)
} else {
memo.subCategories.push(dTransposed)
}
return memo
}, {}))
console.log(result)

Transform array to object tree [JS]

People!
It's my first question here as junior frontend dev.
I have function (https://jsfiddle.net/kmjhsbt9/) which transforms a flat array like this :
const filePaths = [
'src/lib/git.js',
'src/lib/server.js',
'build/css/app.css',
'externs/test/jquery.js',
];
into object tree like this:
[
src: {
lib: {
git.js: 'file',
server.js: 'file',
}
},
build: {
css: {
app.css: 'file'
}
}
.....
]
Please, help me to understand how I can rewrite the function so that it outputs the result in this format:
[
{
text: src,
children: [
{
text: 'lib',
children: [
{
text: git.js,
children: [
{
text: 'file'
},
]
},
{
text: server.js,
children: [
{
text: 'file'
},
]
}
]
}
]
},
{
text: build,
children: [
{
text: app.css,
children: [
text: app.css,
children: [
{
text: 'file'
}
]
]
}
]
}
.....
]
function:
const getTree = (arr) => {
let fileTree = [];
function mergePathsIntoFileTree(prevDir, currDir, i, filePath) {
if (!prevDir.hasOwnProperty(currDir)) {
prevDir[currDir] = {};
}
if (i === filePath.length - 1) {
prevDir[currDir] = 'file';
}
return prevDir[currDir];
}
function parseFilePath(filePath) {
let fileLocation = filePath.split('/');
if (fileLocation.length === 1) {
return (fileTree[fileLocation[0]] = 'file');
}
fileLocation.reduce(mergePathsIntoFileTree, fileTree);
}
arr.forEach(parseFilePath);
return fileTree;
}
Thank you very much, in advance!
You can achieve this by recursively iterating over the keys of the output object (the one that gets generated by getTree):
const filePaths = [
'src/lib/git.js',
'src/lib/server.js',
'build/css/app.css',
'externs/test/jquery.js',
];
// building the dependecy tree,
//an alternative version to your "getTree" function, but a bit cleaner
function getTree(filePaths) {
return filePaths.reduce((all, item) => {
let pointer = null;
item.split('/').forEach((el, i, arr) => {
if (!i) pointer = all;
if (!pointer.hasOwnProperty(el)) {
pointer[el] = (i === arr.length - 1) ? 'file' : {};
}
pointer = pointer[el];
});
return all;
}, {});
}
// formatting the dependency tree to match the desired output with recursion
function buildChildTree(obj) {
return Object.keys(obj).map(key => {
return {
text: key,
children: obj[key] === 'file' ? [{text:'file'}] : buildChildTree(obj[key])
}
});
}
const dependencyTree = getTree(filePaths);
const result = buildChildTree(dependencyTree);
console.log(JSON.stringify(result, null, 2));

Converting an array of objects (with string array elements) into a nested tree

I've been trying to solve a problem with arrays and objects for some time now - but I still can't find the right solution.
This is my starting position:
list = [
{
lastItemData: {data:"root"},
path: ["Root"]
},
{
lastItemData: {data:"level2_Summer"},
path: ["Root", "Level2_Summer"]
},
{
lastItemData: {data:"Level3_Monday"},
path: ["Root", "Level2_Winter", "Level3_Monday"]
},
{
lastItemData: {data:"Level4_Morning"},
path: ["Root", "Level2_Spring", "Level3_Tuesday", "Level4_Morning"]
},
{
lastItemData: {data:"Level3_Sunday"},
path: ["Root", "Level2_Autumn", "Level3_Sunday"]
}]
and this is what i need:
result = [
{
text: "Root",
lastItemData: {data:"root"},
Items:[
{
text:"Level2_Summer",
lastItemData: {data:"level2_Summer"},
Items: []
},
{
text:"Level2_Winter",
Items:[
{
text: "Level3_Monday",
lastItemData: {data:"Level3_Monday"},
Items: []
}
]
},
{
text:"Level2_Spring",
Items:[
{
text: "Level3_Tuesday"
Items: [
{
text:"Level4_Morning"
Items:[],
lastItemData: {data:"Level4_Morning"},
}
]
}
]
},
{
text:"Level2_Autumn",
Items:[
{
text: "Level3_Sunday"
}
]
},
]
}]
I'v try something like this (code is based on user jonas answer from my deleted post)
const property = list.reduce((previous, current, index) => {
let acc = {}; //the accumulator that will go deep into the object
const result = acc; //our result will be the top level object
debugger;
//Now we iterate over our array
for (var i = 0; i < current.stringPath.length; i++) {
debugger;
//add the string part
acc["level" + (i + 1)] = current.stringPath[i];
//Add the children array
acc["Items"] = [{}];
//And go into the object
acc = acc["Items"][0];
}
console.log(result)
previous.push(result);
return previous;
}, []);
console.log("property", property);
But unfortunately the result does not correspond to the desired structure. Can anyone help me?
This is a far more complex problem as it looks like, because you have to build a nested structure with a given name (text property) and you need to check if that name exist or not. If not create a new object and push it to the parent array.
This solution features a hash table where all nested hashes for each level are collected and maintained. The result is an array with the wanted tree.
It works as follows:
First have a look into the root of the hash table and check if the path value exists. If not, create a new node with the information and push it to the hash collector, denoted with _. Then return that node and go on with iteration of the given path.
At the and of the path use lastItemData for the wanted object.
var list = [{ lastItemData: { data: "root" }, path: ["Root"] }, { lastItemData: { data: "level2_Summer" }, path: ["Root", "Level2_Summer"] }, { lastItemData: { data: "Level3_Monday" }, path: ["Root", "Level2_Winter", "Level3_Monday"] }, { lastItemData: { data: "Level4_Morning" }, path: ["Root", "Level2_Spring", "Level3_Tuesday", "Level4_Morning"] }, { lastItemData: { data: "Level3_Sunday" }, path: ["Root", "Level2_Autumn", "Level3_Sunday"] }],
tree = function (array) {
var result = [],
hash = { _: { Items: result } };
array.forEach(function (object) {
object.path.reduce(function (o, p) {
if (!o[p]) {
o[p] = { _: { text: p, Items: [] } };
o._.Items.push(o[p]._);
}
return o[p];
}, hash)._.lastItemData = object.lastItemData;
});
return result;
}(list);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources