Transforming associative tree style array to a normal array recursively - javascript

I've an associative array from data source (which I don't control) like this:
{
"name": "foo",
"children": {
"#1": {
"count": 2,
"children": {
"#2": {
"count": 4,
"children": {
"#5": ...
}
},
"#4": {
"count": 3,
"children": {
"#6": ...
}
}
}
}
}
}
As you can see it's an ever-expanding tree structure using ids as keys. I would like to transform this into normal array so I can use lodash in the browser for a bit easier/faster manipulation.
The results should look like this:
{
"name": "foo",
"children": [
{
"id": "#1",
"count": 2,
"children": [
{
"id": "#2",
"count": 4,
"children": [...]
},
{
"id": "#4",
"count": 3,
"children": [...]
}
]
},
]
}
Transforming the objects isn't the problem but what I can't solve is how to do this recursively so that the structure doesn't get flatten and all children are under the right parent. I went through previous posts here but couldn't find anything that would help with this type of transformation.

You could use Object.keys (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) and a recursive function, like so
var a = {
"name": "foo",
"children": {
"#1": {
"count": 2,
"children": {
"#2": {
"count": 4,
"children": {
"#5": {}
}
},
"#4": {
"count": 3,
"children": {
"#6": {}
}
}
}
}
}
}
function convertKeysToArray(obj) {
if (obj.children !== undefined) {
var arr = []
Object.keys(obj.children).forEach(function(key) {
var child = obj.children[key];
convertKeysToArray(child);
child.id = key;
arr.push(child)
})
obj.children = arr;
}
}
convertKeysToArray(a)
console.log(JSON.stringify(a, null, " "))
var a = {
"name": "foo",
"children": {
"#1": {
"count": 2,
"children": {
"#2": {
"count": 4,
"children": {
"#5": {}
}
},
"#4": {
"count": 3,
"children": {
"#6": {}
}
}
}
}
}
}
function convertKeysToArray(obj) {
if (obj.children !== undefined) {
var arr = []
Object.keys(obj.children).forEach(function(key) {
var child = obj.children[key];
convertKeysToArray(child);
child.id = key;
arr.push(child)
})
obj.children = arr;
}
}
convertKeysToArray(a)
so.log(a)
<pre id="so"></pre>
<script>
var so = {
log: function (obj) {
document.getElementById("so").innerHTML = JSON.stringify(obj, null, " ");
}
}
</script>

Related

Recursion not returning right result

I am working on a problem where I would like to transform the following object structure:
[
{
"label": "testType",
"categories": [
{
"label": "testCatType",
"subCategories": [
{
"label": "newSubCat",
"subSubCategories": [
{
"label": "newSubSubCat1"
},
{
"label": "newSubSubCat2"
},
{
"label": "newSubSubCat3"
}
]
}
]
}
]
},
{
"label": "newType",
"categories": [
{
"label": "newCat10",
"subCategories": [
{
"label": "newCatSub1",
"subSubCategories": [
{
"label": "bingo11"
},
{
"label": "bingo12"
},
{
"label": "bingo15"
}
]
}
]
}
]
},
{
"label": "displacement",
"categories": []
},
{
"label": "brush",
"categories": [
{
"label": "blood",
"subCategories": []
},
{
"label": "damage",
"subCategories": []
},
]
}
]
into something like this:
{
"testType": {
"testCatType": {
"newSubCat": {
"newSubSubCat1": {},
"newSubSubCat2": {},
"newSubSubCat3": {}
}
}
},
"newType": {
"newCat10": {
"newCatSub1": {
"bingo11": {},
"bingo12": {},
"bingo15": {}
}
}
},
"displacement": {},
....
}
I have implemented the following recursive solution for this:
recursiveAssign(obj, json) {
if (_.isUndefined(obj)) {
return;
}
let totalKeys = _.keys(obj);
return _.assign(json, { [obj['label']]: this.test(obj[totalKeys[1]], json) })
}
transform(typeObj){
let json = {};
_.forEach(typeObj, obj => {
let totalKeys = _.keys(obj);
if (totalKeys < 2) {
_.assign(json, obj['label']);
} else {
_.assign(json, { [obj['label']]: this.recursiveAssign(obj[totalKeys[1]], json) })
}
})
}
Now the end result of this is an object that is a copy of itself on each level and I don't quite understand what the problem is. I think my approach is not faulty as I take the label and call the recursive function on the other part of that object. Can please someone point out to the potential issue!
You can write transform as a simple recursive function -
function transform (all = []) {
return Object.fromEntries(all.map(t =>
[t.label, transform(t.categories ?? t.subCategories ?? t.subSubCategories)]
))
}
const data =
[{"label":"testType","categories":[{"label":"testCatType","subCategories":[{"label":"newSubCat","subSubCategories":[{"label":"newSubSubCat1"},{"label":"newSubSubCat2"},{"label":"newSubSubCat3"}]}]}]},{"label":"newType","categories":[{"label":"newCat10","subCategories":[{"label":"newCatSub1","subSubCategories":[{"label":"bingo11"},{"label":"bingo12"},{"label":"bingo15"}]}]}]},{"label":"displacement","categories":[]},{"label":"brush","categories":[{"label":"blood","subCategories":[]},{"label":"damage","subCategories":[]},]}]
console.log(JSON.stringify(transform(data), null, 2))
{
"testType": {
"testCatType": {
"newSubCat": {
"newSubSubCat1": {},
"newSubSubCat2": {},
"newSubSubCat3": {}
}
}
},
"newType": {
"newCat10": {
"newCatSub1": {
"bingo11": {},
"bingo12": {},
"bingo15": {}
}
}
},
"displacement": {},
"brush": {
"blood": {},
"damage": {}
}
}
You can chain ... ?? t.subSubSubCategories and ... ?? t.subSubSubSubCategories, if needed. A better approach would be to create a consistent node interface for all nodes in your graph. You could achieve this by renaming all sub*Categories to simply categories. This will give each node an inteface like
Node: { label: String, categories: Array<Node> }
You can solve this in pure js like this:
const arr = [{ "label": "testType", "categories": [{ "label": "testCatType", "subCategories": [{ "label": "newSubCat", "subSubCategories": [{ "label": "newSubSubCat1" }, { "label": "newSubSubCat2" }, { "label": "newSubSubCat3" }] }] }] }, { "label": "newType", "categories": [{ "label": "newCat10", "subCategories": [{ "label": "newCatSub1", "subSubCategories": [{ "label": "bingo11" }, { "label": "bingo12" }, { "label": "bingo15" }] }] }] }, { "label": "displacement", "categories": [] }, { "label": "brush", "categories": [{ "label": "blood", "subCategories": [] }, { "label": "damage", "subCategories": [] }, ] }];
let result = recursive(arr);
console.log(result);
function recursive(arr) {
let result = {};
let returnValue = {}
for (let obj of arr) {
let entries = Object.entries(obj);
if (!entries.length) {
return {};
}
for (let [key, value] of entries) {
if (typeof value == "object") {
result = recursive(value);
}
}
const label = obj["label"];
returnValue = { ...returnValue,
[obj["label"]]: result
};
}
return returnValue;
}
.as-console-wrapper {min-height: 100%;}

Best way to reorganize array of objects

I need reorganize and array of linked objects by id to only one tree object. The depth level is unknown, so that I think that it should be done recursively. What is the most efficient way?
I have the next array of objects:
const arrObj = [
{
"id": 1,
"children": [
{
"id": 2
},
{
"id": 3
}
]
},
{
"id": 2,
"children": [
{
"id": 4
},
{
"id": 5
}
]
},
{
"id": 3,
"children": [
{
"id": 6
}
]
},
{
"id": 4
}
]
I want restructure for have a only one object like a tree:
const treeObj = {
"id": 1,
"children": [
{
"id": 2,
"children": [
{
"id": 4
},
{
"id": 5
}
]
},
{
"id": 3,
"children": [
{
"id": 6
}
]
}
]
}
Each object has other many properties.
You can use a recursive mapping function over all the children.
const arrObj = [ { "id": 1, "children": [ { "id": 2 }, { "id": 3 } ] }, { "id": 2, "children": [ { "id": 4 }, { "id": 5 } ] }, { "id": 3, "children": [ { "id": 6 } ] }, { "id": 4 } ];
const res = arrObj[0];//assuming the first element is the root
res.children = res.children.map(function getChildren(obj){
const child = arrObj.find(x => x.id === obj.id);
if(child?.children) child.children = child.children.map(getChildren);
return child || obj;
});
console.log(res);

How to delete / prune all tree nodes where the nodes do not exist in a separate array

I have the following JSON tree representation:
let tree = [
{
"label": "Org01",
"children": [
{
"label": "Dist01",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
},
{
"label": "class2",
"children": []
}
]
},
{
"label": "School1tst",
"children": []
}
]
},
{
"label": "Dist02",
"children": []
}
]
},
{
"label": "contoso01",
"children": [
{
"label": "Dist A",
"children": [
{
"label": "School A",
"children": [
{
"label": "classA",
"children": []
}
]
},
{
"label": "School B",
"children": [
{
"label": "classB",
"children": []
}
]
}
]
},
{
"label": "Dist B",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
}
]
}
]
}
]
}
];
I have a list of nodes in an array like here:
let whitelist = ['class1', 'School1', 'Dist01'];
How can I delete all the nodes from the tree which do not exist in the above array. However parent nodes need to be displayed on the tree if their children are in the whitelist.
Deleting a specific node from the tree is possible for me, but I could not figure out a way to delete all nodes from the tree except the few which are in the array.
Final output required:
output = [
{
"label": "razor01",
"children": [
{
"label": "Dist01",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
}
]
}
]
}
]
},
{
"label": "contoso01",
"children": [
{
"label": "Dist B",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
}
]
}
]
}
]
}];
Thanks and I appreciate any help with this.
const prunedNode = node => {
const pruned = whitelist.includes(node.label) ? node : null;
if (pruned) {
node.children = node.children.reduce((prunedChildren, child) => {
const prunedChildNode = prunedNode(child);
if (prunedChildNode) {
prunedChildren.push(prunedChildNode);
}
return prunedChildren;
}, []);
}
return pruned;
};
console.log(prunedNode(tree));
This should get the job done:
function filter(tree, list){
let output = [];
for(i in tree){
if(list.indexOf(tree[i].label) >= 0){
tree[i].children = filter(tree[i].children, list);
output.push(tree[i]);
}else{
output = output.concat(filter(tree[i].children, list));
}
}
return output;
}
I solved it this way:
function deleteNodes(tree, list) {
if (tree.length > 0) {
tree.forEach((node, i) => {
this.deleteNodes(node.subItems, list);
if (node.subItems) {
if (node.subItems.length === 0 && !list.includes(node.text))
{
tree.splice(i, 1);
}
}
});
}
}

Find object by specific key in a deep nested object | Javascript

What is the cleanest way to find object and return that based on id , If I don't know how many nested object there will be in my object ?
Let's say I have the following structure :
myObj = {
"id": "5e6b8961ba08180001a10bb6",
"children": [
{
"id": "5e6b8961ba08180001a10bb7",
"refrenceId": "SEC-02986",
"children": [
{
"id": "5e58d7bc1bbc71000118c0dc"
},
{
"id": "5e58d7bc1bbc71000118c0dd",
"refrenceId": "SKU-00343"
},
{
"id": "5e590d571bbc71000118c102",
"refrenceId": "SKU-05290"
},
{
"id": "5e590df71bbc71000118c109",
"children": [
{
"id": "5e590df71bbc71000118c10a"
},
{
"id": "5e590df71bbc71000118c10b",
"refrenceId": "SKU-00444"
},
{
"id": "5e5cb9428ae591000177c0f6"
}
]
},
{
"id": "5e81899f0bab450001dcfc1d",
"refrenceId": "SEC-03260"
},
{
"id": "5e81c4b51503860001f97f6c",
"refrenceId": "SEC-03267",
"children": [
{
"id": "5e8ad5175d374200014edb3a",
"refrenceId": "SEC-03409",
"children": [
{
"id": "5e8f28882d94c1000156bebe"
}
]
},
{
"id": "5e8ad5175d374200014edb3c",
"refrenceId": "SEC-03410"
},
{
"id": "5e8f29082d94c1000156bec6",
"refrenceId": "SEC-03495"
}
]
}
]
}
Suppose, I want to find the one with id "5e590df71bbc71000118c10b", and return that object from nested object.
I have tried using following code:
function nodeHasChildren(children, id) {
for (const child of children) {
if (child.id === id) {
if (Array.isArray(child.children) && child.children.length > 0) {
return child;
}
}
else {
const result = nodeHasChildren(child.children, id);
if (result !== undefined) {
return result
}
}
}
}
console.log(nodeWithIdHasChildren(myObj, "5e590df71bbc71000118c10b"));
Use simple recursion
function findDeepById(node, id) {
if (node.id === id) return node;
if (node.children) {
for(const child of node.children){
const match = findDeepById(child, id);
if (match) return match;
}
}
}
const myObj = {
"id": "5e6b8961ba08180001a10bb6",
"children": [{
"id": "5e6b8961ba08180001a10bb7",
"refrenceId": "SEC-02986",
"children": [{
"id": "5e58d7bc1bbc71000118c0dc"
},
{
"id": "5e58d7bc1bbc71000118c0dd",
"refrenceId": "SKU-00343"
},
{
"id": "5e590d571bbc71000118c102",
"refrenceId": "SKU-05290"
},
{
"id": "5e590df71bbc71000118c109",
"children": [{
"id": "5e590df71bbc71000118c10a"
},
{
"id": "5e590df71bbc71000118c10b",
"refrenceId": "SKU-00444"
},
{
"id": "5e5cb9428ae591000177c0f6"
}
]
},
{
"id": "5e81899f0bab450001dcfc1d",
"refrenceId": "SEC-03260"
},
{
"id": "5e81c4b51503860001f97f6c",
"refrenceId": "SEC-03267",
"children": [{
"id": "5e8ad5175d374200014edb3a",
"refrenceId": "SEC-03409",
"children": [{
"id": "5e8f28882d94c1000156bebe"
}]
},
{
"id": "5e8ad5175d374200014edb3c",
"refrenceId": "SEC-03410"
},
{
"id": "5e8f29082d94c1000156bec6",
"refrenceId": "SEC-03495"
}
]
}
]
}]
};
function findDeepById(node, id) {
if (node.id === id) return node;
if (node.children) {
for(const child of node.children){
const match = findDeepById(child, id);
if (match) return match;
}
}
}
console.log(findDeepById(myObj, "5e590df71bbc71000118c10b"));
In short, this part was the biggest problem:
if (child.id === id) {
if (Array.isArray(child.children) && child.children.length > 0) {
return child;
}
}
You've found your object, why look for its children?
ORIGINAL ANSWER/WORKING SOLUTION
For start, you need to make your original object iterable because that's what your function expects, I've done so by simply making it an array like nodeHasChildren([myObj], ... when calling the function. Then, after some cleanup and fixing the logic mentioned above, we get this (added relevant comments to the code below):
myObj = {
"id": "5e6b8961ba08180001a10bb6",
"children": [{
"id": "5e6b8961ba08180001a10bb7",
"refrenceId": "SEC-02986",
"children": [{
"id": "5e58d7bc1bbc71000118c0dc"
},
{
"id": "5e58d7bc1bbc71000118c0dd",
"refrenceId": "SKU-00343"
},
{
"id": "5e590d571bbc71000118c102",
"refrenceId": "SKU-05290"
},
{
"id": "5e590df71bbc71000118c109",
"children": [{
"id": "5e590df71bbc71000118c10a"
},
{
"id": "5e590df71bbc71000118c10b",
"refrenceId": "SKU-00444"
},
{
"id": "5e5cb9428ae591000177c0f6"
}
]
},
{
"id": "5e81899f0bab450001dcfc1d",
"refrenceId": "SEC-03260"
},
{
"id": "5e81c4b51503860001f97f6c",
"refrenceId": "SEC-03267",
"children": [{
"id": "5e8ad5175d374200014edb3a",
"refrenceId": "SEC-03409",
"children": [{
"id": "5e8f28882d94c1000156bebe"
}]
},
{
"id": "5e8ad5175d374200014edb3c",
"refrenceId": "SEC-03410"
},
{
"id": "5e8f29082d94c1000156bec6",
"refrenceId": "SEC-03495"
}
]
}
]
}]
}
function nodeHasChildren(children, id) {
for (const child of children) {
if (child.id === id) {
// solution found, no need to do anything else
console.log("SOLUTION: ");
return child;
} else {
// check if it has children and iterate over them recursively
if (Array.isArray(child.children) && child.children.length > 0)
var result = nodeHasChildren(child.children, id);
// check if the result was found in children in the line above
if (result != undefined)
return result;
}
}
}
console.log(nodeHasChildren([myObj], "5e590df71bbc71000118c10b"));
console.log(nodeHasChildren([myObj], "5e8ad5175d374200014edb3c"));
We now use object-scan for simple data processing tasks. It's pretty awesome once you wrap your head around it. Here is how you could answer your questions
// const objectScan = require('object-scan');
const find = (id, input) => objectScan(['**'], {
rtn: 'value',
abort: true,
filterFn: ({ value }) => value.id === id
})(input);
const myObj = { id: '5e6b8961ba08180001a10bb6', children: [{ id: '5e6b8961ba08180001a10bb7', refrenceId: 'SEC-02986', children: [{ id: '5e58d7bc1bbc71000118c0dc' }, { id: '5e58d7bc1bbc71000118c0dd', refrenceId: 'SKU-00343' }, { id: '5e590d571bbc71000118c102', refrenceId: 'SKU-05290' }, { id: '5e590df71bbc71000118c109', children: [{ id: '5e590df71bbc71000118c10a' }, { id: '5e590df71bbc71000118c10b', refrenceId: 'SKU-00444' }, { id: '5e5cb9428ae591000177c0f6' }] }, { id: '5e81899f0bab450001dcfc1d', refrenceId: 'SEC-03260' }, { id: '5e81c4b51503860001f97f6c', refrenceId: 'SEC-03267', children: [{ id: '5e8ad5175d374200014edb3a', refrenceId: 'SEC-03409', children: [{ id: '5e8f28882d94c1000156bebe' }] }, { id: '5e8ad5175d374200014edb3c', refrenceId: 'SEC-03410' }, { id: '5e8f29082d94c1000156bec6', refrenceId: 'SEC-03495' }] }] }] };
console.log(find('5e8ad5175d374200014edb3a', myObj));
/* =>
{ id: '5e8ad5175d374200014edb3a',
refrenceId: 'SEC-03409',
children: [ { id: '5e8f28882d94c1000156bebe' } ] }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Building new JSON from existing one

I want to build an new JSON from existing one. The source has sections and rubrics that I no longer need for a listing. The new object called 'items' should have an array of the items.
The final JSON should be sorted by attribute 'name' and look like
{
"items": [
{
"id": 10000006,
"name": "Boah"
},
{
"id": 10000013,
"name": "Gut"
},
{
"id": 10000003,
"name": "Ipsum"
},
{
"id": 10000001,
"name": "Lorem"
},
{
"id": 10000005,
"name": "Lorum"
},
{
"id": 10000004,
"name": "Name"
},
{
"id": 10000002,
"name": "Stet"
}
]
}
For building the new JSON I get this source:
{
"sections": [
{
"name": "FooBar",
"rubrics": [
{
"name": "Foo",
"items": [
{
"id": 10000001,
"name": "Lorem"
},
{
"id": 10000002,
"name": "Stet"
},
{
"id": 10000003,
"name": "Ipsum"
}
]
},
{
"name": "Bar",
"items": [
{
"id": 10000004,
"name": "Name"
},
{
"id": 10000005,
"name": "Lorum"
},
{
"id": 10000006,
"name": "Boah"
}
]
}
]
},
{
"name": "BlahBloob",
"rubrics": [
{
"name": "Bla",
"items": [
{
"id": 10000013,
"name": "Gut"
}
]
},
{
"name": "Bloob",
"items": [
{
"id": 10000014,
"name": "Name"
},
{
"id": 10000015,
"name": "Lorem"
}
]
}
]
}
]
}
What do you think? How can I do this with plain JavaScript or maybe TypeScript?
Thanks for reading and have time for my question. And thanks for reply in advance.
Here you go. You just need to iterate over each rubric of each section of your source to get the items. At the end, sort your list of items by items, and you're done.
This example uses ES6 syntax, but it's easy to convert it to ES5 if needed.
function extractItems(source) {
const items = [];
for (const section of source.sections) {
for (const rubric of section.rubrics) {
items.push(...rubric.items);
}
}
items.sort((a, b) => a.name.localeCompare(b.name));
return { items };
}
A more functional approach use map and reduce to pick the rubrics and merge them.
data.sections
.map(section => section.rubrics) // get rubrics
.reduce((a, b) => a.concat(b)) // merge rubrics
.map(rubric => rubric.items) // get items from each rubric
.reduce((a, b) => a.concat(b)) // merge items
.sort((a, b) => a.name.localeCompare(b.name)); // sort
function(oldObj) {
var newObj = {
"items": []
};
oldObj.sections.forEach(function(section) {
section.rubrics.forEach(function(rubric) {
rubric.items.forEach(function(item) {
newObj.items.push(item);
});
});
});
newObj.items = newObj.items.sort(function(a, b) {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return newObj;
}
And simply use JSON.parse() and JSON.stringify() to convert JSON to and from objects.
It might help you
var data ={
"sections": [
{
"name": "FooBar",
"rubrics": [{"name": "Foo", "items": [{"id": 10000001,"name": "Lorem"}, {"id": 10000002,"name": "Stet"}, {"id": 10000003,"name": "Ipsum"}]
}, {
"name": "Bar",
"items": [{
"id": 10000004,
"name": "Name"
}, {
"id": 10000005,
"name": "Lorum"
}, {
"id": 10000006,
"name": "Boah"
}]
}]
}, {
"name": "BlahBloob",
"rubrics": [{
"name": "Bla",
"items": [{
"id": 10000013,
"name": "Gut"
}]
}, {
"name": "Bloob",
"items": [{
"id": 10000014,
"name": "Name"
}, {
"id": 10000015,
"name": "Lorem"
}]
}]
}]
};
var itemObj = {};
var itemArr = [];
var sections = data.sections;
for(var i=0;i<sections.length;i++)
{
for(var j=0;j<sections[i].rubrics.length;j++){
for(var k=0;k<sections[i].rubrics[j].items.length;k++){
var itemObj;
itemObj['id'] = sections[i].rubrics[j].items[k].id;
itemObj['name'] = sections[i].rubrics[j].items[k].name;
itemArr.push(itemObj);
}
}
}
var finalObj = {"items":itemArr};
console.log(finalObj);
JSFiddle

Categories

Resources