Find object by specific key in a deep nested object | Javascript - 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

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%;}

Javascript or lodash nested JSON object transformation

How to make nested object null
if object attributes has a null values. please check the JSON below
I have JSON data as following
[{
"student": {
"id": null,
"name": null
},
"children": [{
"student": null,
"children": [{
"student": {
"id": 1,
"name": "A"
}
},
{
"student": {
"id": null,
"name": null
}
}
]
}]
}]
I want to convert it to following output
Expected
[{
"student": null,
"children": [{
"student": null,
"children": [{
"student": {
"id": 1,
"name": "A"
}
},
{
"student": null
}
]
}]
}]
You could "nullify" values if their values are all null using the following conditional.
Object.values(obj).every(value => value == null)
I created a basic recursive function to below that traverses the object and checks to see if it needs to nullify an object. This is all done in-place, and it will modify the original object.
const obj = [{
"student": { "id": null, "name": null },
"children": [{
"student": null,
"children": [
{ "student": { "id": 1, "name": "A" } },
{ "student": { "id": null, "name": null } }
]
}]
}];
const nullify = (obj, key = null, parent = null) => {
if (obj != null) {
if (Array.isArray(obj)) {
obj.forEach((item, index) => nullify(item, index, obj));
} else if (typeof obj === 'object') {
if (Object.values(obj).every(value => value == null)) {
parent[key] = null;
} else {
Object.entries(obj).forEach(([key, value]) => nullify(value, key, obj));
}
}
}
};
nullify(obj);
console.log(obj);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Here is an iterative solution using object-scan
// const objectScan = require('object-scan');
const data = [{ student: { id: null, name: null }, children: [{ student: null, children: [{ student: { id: 1, name: 'A' } }, { student: { id: null, name: null } }] }] }];
const nullify = objectScan(['**(^children$).student'], {
useArraySelector: false,
rtn: 'count',
filterFn: ({ parent, property, value }) => {
if (
value instanceof Object
&& !Array.isArray(value)
&& Object.values(value).every((e) => e === null)
) {
parent[property] = null;
return true;
}
return false;
}
});
console.log(nullify(data));
// => 2
console.log(data);
// => [ { student: null, children: [ { student: null, children: [ { student: { id: 1, name: 'A' } }, { student: null } ] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#14.3.1"></script>
Disclaimer: I'm the author of object-scan

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);

Find the parent of the searched value using javascript

I have a JSON data in the following format which needs to be filtered based on a specific value :
[
{
"id": 0,
"name": "ROOT-0",
"childs": [
{
"id": 1,
"name": "ROOT-1",
"childs": [
{
"id": 11,
"name": "ROOT-11",
},
{
"id": 12,
"name": "ROOT-12",
},
]
},
{
"id": 2,
"name": "ROOT-2",
"childs": [
{
"id": 21,
"name": "ROOT-21",
},
{
"id": 22,
"name": "ROOT-22",
},
]
},
{
"id": 3,
"name": "ROOT-3",
"childs": [
{
"id": 31,
"name": "ROOT-31",
},
{
"id": 32,
"name": "ROOT-32",
},
]
}
]
}]
The scenario is that I need to get ROOT-1 as final result if I look for ROOT-11/ROOT-12.
I have tried filtering with this following code
var res = data[0].filter(function f(o) {
if (o.name.includes("ROOT-11")) return o;
})
But I am not able to get a grip on the logic. Is there a way to achieve my desired output
You could use find()...
var result = data[0].childs.find(x => {
return x.childs.find(y => {
return y.name === name;
});
}).name;
Or you could write a function...
function findParentName(name, data) {
return data[0].childs.find(x => {
return x.childs.find(y => {
return y.name === name;
});
}).name;
}
var result = findParentName('ROOT-11', data);
console.log(result);
Doing this will give you the best performance result as find() will return as soon as it finds a match, and not iterate through each remaining loop like forEach() or map()
If you're using ES6 you can say...
const result = data[0].childs.find(x => x.childs.find(y => y.name === 'ROOT-11')).name;
You can grab the item using a few filters and a find, to get the result you are looking for:
let items = [{
"id": 0,
"name": "ROOT-0",
"childs": [{
"id": 1,
"name": "ROOT-1",
"childs": [{
"id": 11,
"name": "ROOT-11",
},
{
"id": 12,
"name": "ROOT-12",
},
]
},
{
"id": 2,
"name": "ROOT-2",
"childs": [{
"id": 21,
"name": "ROOT-21",
},
{
"id": 22,
"name": "ROOT-22",
},
]
},
{
"id": 3,
"name": "ROOT-3",
"childs": [{
"id": 31,
"name": "ROOT-31",
},
{
"id": 32,
"name": "ROOT-32",
},
]
}
]
}]
function find(name) {
let result
items.filter(item =>
result = item.childs.find(item2 =>
item2.childs.filter(i => i.name == name).length > 0
)
)
return result.name || ''
}
console.log(find('ROOT-11'))
console.log(find('ROOT-22'))
console.log(find('ROOT-32'))
You could, for an arbitrary count nested children, use a recusion approach by iterating the actual level and if not found check the children with the actual name.
If the wanted name is found, the parent's name is handed over through all nested calls and returned.
function getParent(array, search, parent) {
return array.some(o => o.name === search || o.children && (parent = getParent(o.children, search, o.name)))
&& parent;
}
var data = [{ id: 0, name: "ROOT-0", children: [{ id: 1, name: "ROOT-1", children: [{ id: 11, name: "ROOT-11" }, { id: 12, name: "ROOT-12" }] }, { id: 2, name: "ROOT-2", children: [{ id: 21, name: "ROOT-21" }, { id: 22, name: "ROOT-22" }] }, { id: 3, name: "ROOT-3", children: [{ id: 31, name: "ROOT-31" }, { id: 32, name: "ROOT-32" }] }] }]
console.log(getParent(data, 'ROOT-0')); // undefined no parent found
console.log(getParent(data, 'ROOT-1')); // ROOT-0
console.log(getParent(data, 'ROOT-11')); // ROOT-1
console.log(getParent(data, 'ROOT-31')); // ROOT-3
.as-console-wrapper { max-height: 100% !important; top: 0; }

Transforming associative tree style array to a normal array recursively

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>

Categories

Resources