Search for all paths to the value in JavaScript object - javascript

I have a complex JavaScript object given below.
An example object:
var object= {
"name": "tfifkhul",
"id": "262761",
"children": [
{
"name": "rthrth",
"id": 0,
"children": [
{
"name": "test",
"id": "262762",
"children": []
}
]
},
{
"name": "rthsrth",
"id": 0,
"children": [
{
"name": "test",
"id": "262762",
"children": []
}
]
},
{
"name": "rthrthhrth",
"id": 0,
"children": [
{
"name": "test",
"id": "262762",
"children": [
{
"name": "rtjrtj",
"id": 0,
"children": [
{
"name": "fwefwefwef",
"id": "262768",
"children": []
}
]
},
{
"name": "hsrtjrtdjrtj",
"id": 0,
"children": [
{
"name": "we4yhesrhy",
"id": "262764",
"children": []
}
]
},
{
"name": "lol",
"id": "262763",
"children": [
{
"name": "fwefwefwef",
"id": "262768",
"children": [
{
"name": "87ok78",
"id": "262765",
"children": [
{
"name": "78o78",
"id": 0,
"children": [
{
"name": "we4yhesrhy",
"id": "262764",
"children": [
{
"name": "test1",
"id": 0,
"children": [
{
"name": "",
"id": "262766",
"children": []
}
]
},
{
"name": "test2",
"id": 0,
"children": [
{
"name": "",
"id": "262766",
"children": []
}
]
}
]
}
]
},
{
"name": "7o78o76o8",
"id": 0,
"children": [
{
"name": "",
"id": "262766",
"children": []
}
]
},
{
"name": "ko",
"id": 0,
"children": [
{
"name": "",
"id": "262767",
"children": []
}
]
}
]
}
]
}
]
}
]
}
]
}
]
};
I need to create a function to search for all matching values for key "id" with given value.
So far I have created one recursive function:
function searchOccurances(theObject, value,path) {
var result = null;
if(theObject instanceof Array) {
for(var i = 0; i < theObject.length; i++) {
result = searchOccurances(theObject[i],value,path+","+i);
}
}
else
{
for(prop in theObject) {
if(prop == 'id') {
if(theObject[prop] == value) {
keyOccurances.push(path);
}
}
if((theObject[prop] instanceof Array) || (theObject[prop] instanceof Object))
{
if((theObject[prop].length!=undefined)&&(theObject[prop].length!=0))
{
result = searchOccurances(theObject[prop],value,path+","+prop);
}
}
}
}
return result;
}
keyOccurances=[];
searchOccurances(object,262762,'');
console.log(keyOccurances);
//Output
[",children,0,children,0", ",children,1,children,0", ",children,2,children,0"] -- correct
keyOccurances=[];
searchOccurances(object,262768,'');
console.log(keyOccurances);
//Output
[",children,1,children,0,children,1,children,0", ",children,1,children,0,children,2,children,0"] --wrong
The function returns array of comma separated paths of matched value but doesn't seems to be getting right results. For the first call with value '262762' gives corrects path list but for value '262768' gives incorrect path list.
Kindly help.

I'd suggest to provide a better test object. Would you really have so many children with 'id = 0' in a real use case? Would you have 2 children with the same ID at all? That makes things pretty hard to debug.
Below is an example function that should work as expected.
function search(object, value) {
var res = [], searchPath;
(searchPath = function(children, path) {
var n, newPath;
for(n in children) {
if(typeof children[n].id !== 'undefined' && parseInt(children[n].id, 10) === value) {
res.push(path);
}
newPath = path.slice();
newPath.push(children[n].id);
searchPath(children[n].children, newPath);
}
})([ object ], []);
return res;
}
console.log(search(object, 262762));
console.log(search(object, 262768));
Output:
[["262761", 0], ["262761", 0], ["262761", 0]]
[["262761", 0, "262762", 0], ["262761", 0, "262762", "262763"]]
The above code is not (yet) bullet-proof but hopefully is it short enough to be easily understandable.

If I understand your questions correctly, you're looking for all paths where a specific id is present. I'd recommend not reinventing the wheel here and using an existing library. We use object-scan for most of our data processing now. It's powerful once you wrap your head around it. Here is how you'd answer your question
// const objectScan = require('object-scan');
const findKeys = (haystack, id) => objectScan(['**'], {
joined: true,
filterFn: ({ value }) => value.id === id
})(haystack);
const object = { name: 'tfifkhul', id: '262761', children: [{ name: 'rthrth', id: 0, children: [{ name: 'test', id: '262762', children: [] }] }, { name: 'rthsrth', id: 0, children: [{ name: 'test', id: '262762', children: [] }] }, { name: 'rthrthhrth', id: 0, children: [{ name: 'test', id: '262762', children: [{ name: 'rtjrtj', id: 0, children: [{ name: 'fwefwefwef', id: '262768', children: [] }] }, { name: 'hsrtjrtdjrtj', id: 0, children: [{ name: 'we4yhesrhy', id: '262764', children: [] }] }, { name: 'lol', id: '262763', children: [{ name: 'fwefwefwef', id: '262768', children: [{ name: '87ok78', id: '262765', children: [{ name: '78o78', id: 0, children: [{ name: 'we4yhesrhy', id: '262764', children: [{ name: 'test1', id: 0, children: [{ name: '', id: '262766', children: [] }] }, { name: 'test2', id: 0, children: [{ name: '', id: '262766', children: [] }] }] }] }, { name: '7o78o76o8', id: 0, children: [{ name: '', id: '262766', children: [] }] }, { name: 'ko', id: 0, children: [{ name: '', id: '262767', children: [] }] }] }] }] }] }] }] };
console.log(findKeys(object, '262762'));
/* =>
[ 'children[2].children[0]',
'children[1].children[0]',
'children[0].children[0]' ]
*/
console.log(findKeys(object, '262768'));
/* =>
[ 'children[2].children[0].children[2].children[0]',
'children[2].children[0].children[0].children[0]' ]
*/
.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
Edit: The accepted answer didn't make much sense to me (based on the question), but here is how you could generate the same output
// const objectScan = require('object-scan');
const findIdPath = (haystack, id) => objectScan(['**'], {
reverse: false,
filterFn: ({ value, parents, context }) => {
if (value.id === id) {
context.push(parents.filter((p) => 'id' in p).map((p) => p.id).reverse());
}
}
})(haystack, []);
const object = { name: 'tfifkhul', id: '262761', children: [{ name: 'rthrth', id: 0, children: [{ name: 'test', id: '262762', children: [] }] }, { name: 'rthsrth', id: 0, children: [{ name: 'test', id: '262762', children: [] }] }, { name: 'rthrthhrth', id: 0, children: [{ name: 'test', id: '262762', children: [{ name: 'rtjrtj', id: 0, children: [{ name: 'fwefwefwef', id: '262768', children: [] }] }, { name: 'hsrtjrtdjrtj', id: 0, children: [{ name: 'we4yhesrhy', id: '262764', children: [] }] }, { name: 'lol', id: '262763', children: [{ name: 'fwefwefwef', id: '262768', children: [{ name: '87ok78', id: '262765', children: [{ name: '78o78', id: 0, children: [{ name: 'we4yhesrhy', id: '262764', children: [{ name: 'test1', id: 0, children: [{ name: '', id: '262766', children: [] }] }, { name: 'test2', id: 0, children: [{ name: '', id: '262766', children: [] }] }] }] }, { name: '7o78o76o8', id: 0, children: [{ name: '', id: '262766', children: [] }] }, { name: 'ko', id: 0, children: [{ name: '', id: '262767', children: [] }] }] }] }] }] }] }] };
console.log(findIdPath(object, '262762'));
// => [ [ '262761', 0 ], [ '262761', 0 ], [ '262761', 0 ] ]
console.log(findIdPath(object, '262768'));
// => [ [ '262761', 0, '262762', 0 ], [ '262761', 0, '262762', '262763' ] ]
.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

JS filtering an Array item through a value in a nested Array

I can't figure out how to properly use .filter() to find an object in an Array by searching for a value in its nested Array.
I have the following data:
const orders = [
{
id: 1,
items: [
{
itemId: "abc",
},
{
itemId: "def",
},
],
},
{
id: 2,
items: [
{
itemId: "jkl",
},
{
itemId: "mno",
},
],
},
{
id: 3,
items: [
{
itemId: "abc",
},
{
itemId: "xyz",
},
],
},
];
I have the needed itemId: "abc" which I need to use to find all the objects that have items that also have the Id of "abc", so that the result is this:
[
{
id: 1,
items: [
{
itemId: "abc",
},
{
itemId: "def",
},
],
},
{
id: 3,
items: [
{
itemId: "abc",
},
{
itemId: "xyz",
},
],
},
]
So far, I've tried the following:
orders &&
orders.filter((order) => {
return order.items.filter((item) => item.itemId === "abc");
});
But it doesn't seem to work. What am I missing here?
Chris G beat me to it in the comments but he is right, you need to use order.items.some in your inner function:
const orders = [{
id: 1,
items: [{
itemId: "abc",
},
{
itemId: "def",
},
],
},
{
id: 2,
items: [{
itemId: "jkl",
},
{
itemId: "mno",
},
],
},
{
id: 3,
items: [{
itemId: "abc",
},
{
itemId: "xyz",
},
],
},
]
var ans = orders.filter((order) => {
return order.items.some((item) => item.itemId === "abc");
});
console.log(ans)
const orders = [{
id: 1,
items: [{
itemId: "abc",
},
{
itemId: "def",
},
],
},
{
id: 2,
items: [{
itemId: "jkl",
},
{
itemId: "mno",
},
],
},
{
id: 3,
items: [{
itemId: "abc",
},
{
itemId: "xyz",
},
],
},
];
const result = orders.filter(order =>
order.items.find(item => item.itemId === 'abc') !== undefined
)
console.log(result);

How to get all specific values from an array of objects with nested arrays of objects?

I have an array:
const arr = [
{
name: "name 1",
dontShow: true,
children: [
{
name: "name 2",
key4: 4,
dontShow: false,
children: [],
},
],
},
{
name: "name 3",
dontShow: false,
children: [
{
name: "name 4",
dontShow: true,
children: [
{
name: "name 5",
dontShow: false,
children: null,
},
],
},
],
},
];
I need an array of names from every object, except those that have property dontShow: true
So from that example I would expect such array:
["name2", "name3", "name5"]
Basically, I need to get a flat array from tree-like structure, lodash/underscore solutions would be also great, I just didn't find them
You can use a recursive function
const arr = [{ name: "name 1", dontShow: true, children: [{ name:"name 2", key4: 4, dontShow: false, children: [], }, ],},{name: "name 3",dontShow: false,children: [{ name: "name 4", dontShow: true, children: [{ name: "name 5", dontShow: false, children: null,},],}, ],},];
let final = (arr, result = []) => {
if (Array.isArray(arr)) {
arr.forEach(obj => {
if (!obj.dontShow) {
result.push(obj.name)
}
if (Array.isArray(obj.children)) {
final(obj.children, result)
}
})
}
return result
}
console.log(final(arr))
You could get a flat array of names with a look to dontShow.
const
getNames = array => array.flatMap(({ name, dontShow, children }) => [
...(dontShow ? [] : [name]),
...getNames(children || [])
]),
array = [{ name: "name 1", dontShow: true, children: [{ name: "name 2", key4: 4, dontShow: false, children: [] }] }, { name: "name 3", dontShow: false, children: [{ name: "name 4", dontShow: true, children: [{ name: "name 5", dontShow: false, children: null, }] }] }],
result = getNames(array);
console.log(result);

Find matching nodes in a tree with full path

I have an array of objects like this
[{
name: "Peter",
children: [{
name: "John",
children: [{
name: "Joseph",
children: []
}]
}, {
name: "Shawn",
children: [{
name: "Joseph",
children: []
}]
}]
}, {
name: "Carl",
children: [{
name: "Sam",
children: [{
name: "JohnXX",
children: []
}]
}]
}]
Where each person can have multiple children and each of these children can have any number of children and so on.
I want to retain the full path with matching children and exclude non matching children.For example if I search for John output should be like this
[{
name: "Peter",
children: [{
name: "John",
children: [{
name: "Joseph",
children: []
}]
}]
}, {
name: "Carl",
children: [{
name: "Sam",
children: [{
name: "JohnXX",
children: []
}]
}]
}]
You need to generate a new object with only the relevant parts.
This proposal works iterative for a single level and recursive for the children.
function getNodes(array, cb) {
return array.reduce(function iter(r, a) {
var children;
if (cb(a)) {
return r.concat(a);
}
if (Array.isArray(a.children)) {
children = a.children.reduce(iter, []);
}
if (children.length) {
return r.concat({ name: a.name, children: children });
}
return r;
}, []);
}
var data = [{ name: "Peter", children: [{ name: "John", children: [{ name: "Joseph", children: [] }] }, { name: "Shawn", children: [{ name: "Joseph", children: [] }] }] }, { name: "Carl", children: [{ name: "Sam", children: [{ name: "JohnXX", children: [] }] }] }];
console.log(getNodes(data, function (o) { return o.name.indexOf('John') !== -1; }));
.as-console-wrapper { max-height: 100% !important; top: 0; }

validate json array schema

guys i have this json
var menu = [{
name: 'Computers',
children: [{
name: 'Notebook'
children: [{
name: 'Apple'
}, {
name: 'Windows'
}]
}, {
name: Tablets
children: [{
name: 'Apple'
}, {
name: 'Android'
}, {
name: 'Windows'
}]
}]
}, {
name: 'Phones',
children: [{
name: 'Android'
children: [{
name: 'Samsung'
}, {
name: 'Nokia'
}, {
name: 'Lenovo'
}]
}, {
name: 'Windows Phones'
children: [{
name: 'Microsoft'
}, {
name: 'Nokia'
}]
}]
}, {
name: 'Cameras',
children: [{
name: 'Digital'
children: [{
name: 'Nikon'
}, {
name: 'Fuji'
}]
}, {
name: 'DSLR'
children: [{
name: 'Canon'
}, {
name: 'Nikon'
}]
}]
}];
it says it is not valid json ... so how to make it valid json ??
any help would be appreciated ... thanks a lot
btw i am beginner so please help me
any suggestions ?? thanks again :)
You have some missing commas, where noted and a suposed to be a string without delimiters in your object literal.
var menu = [{
name: 'Computers',
children: [{
name: 'Notebook', // missing ,
children: [{
name: 'Apple'
}, {
name: 'Windows'
}]
}, {
name: 'Tablets', // missing string delimiter and comma
children: [{
name: 'Apple'
}, {
name: 'Android'
}, {
name: 'Windows'
}]
}]
}, {
name: 'Phones',
children: [{
name: 'Android', // missing ,
children: [{
name: 'Samsung'
}, {
name: 'Nokia'
}, {
name: 'Lenovo'
}]
}, {
name: 'Windows Phones', // missing ,
children: [{
name: 'Microsoft'
}, {
name: 'Nokia'
}]
}]
}, {
name: 'Cameras',
children: [{
name: 'Digital', // missing ,
children: [{
name: 'Nikon'
}, {
name: 'Fuji'
}]
}, {
name: 'DSLR', // missing ,
children: [{
name: 'Canon'
}, {
name: 'Nikon'
}]
}]
}];
console.log(menu);
.as-console-wrapper { max-height: 100% !important; top: 0; }
There are two problems
After name property values, comma is missing (at various places)
Tablets is not in quotes
Correct syntax would be.
var menu = [{
name: 'Computers',
children: [{
name: 'Notebook',
children: [{
name: 'Apple'
}, {
name: 'Windows'
}]
}, {
name: 'Tablets',
children: [{
name: 'Apple'
}, {
name: 'Android'
}, {
name: 'Windows'
}]
}]
}, {
name: 'Phones',
children: [{
name: 'Android',
children: [{
name: 'Samsung'
}, {
name: 'Nokia'
}, {
name: 'Lenovo'
}]
}, {
name: 'Windows Phones',
children: [{
name: 'Microsoft'
}, {
name: 'Nokia'
}]
}]
}, {
name: 'Cameras',
children: [{
name: 'Digital',
children: [{
name: 'Nikon'
}, {
name: 'Fuji'
}]
}, {
name: 'DSLR',
children: [{
name: 'Canon'
}, {
name: 'Nikon'
}]
}]
}];
You can copy your code is chrome's console to see where the error is.
you need to put double quotes on key an value like this "key" : "value"
[{
"name": "Computers",
"children": [{
"name": "Notebook",
"children": [{
"name": "Apple"
}, {
"name": "Windows"
}]
}, {
"name": "Tablets",
"children": [{
"name": "Apple"
}, {
"name": "Android"
}, {
"name": "Windows"
}]
}]
}]
And after name property values, comma is missing (at various places)
key and value should be wrapped by double quotes
copy paste the code validate here https://jsonformatter.curiousconcept.com/
[
{
"name":"Computers",
"children":[
{
"name":"Notebook",
"children":[
{
"name":"Apple"
},
{
"name":"Windows"
}
]
},
{
"name":"Tablets",
"children":[
{
"name":"Apple"
},
{
"name":"Android"
},
{
"name":"Windows"
}
]
}
]
},
{
"name":"Phones",
"children":[
{
"name":"Android",
"children":[
{
"name":"Samsung"
},
{
"name":"Nokia"
},
{
"name":"Lenovo"
}
]
},
{
"name":"Windows Phones",
"children":[
{
"name":"Microsoft"
},
{
"name":"Nokia"
}
]
}
]
},
{
"name":"Cameras",
"children":[
{
"name":"Digital",
"children":[
{
"name":"Nikon"
},
{
"name":"Fuji"
}
]
},
{
"name":"DSLR",
"children":[
{
"name":"Canon"
},
{
"name":"Nikon"
}
]
}
]
}
]

Nested Tree structure object trying to extract and get information Json object

I would like to know the correct way to create a nested Json tree Structure object in javascript.
data structure containing objects and arrays. How can I extract the information, i.e. access a specific or multiple values (or id)?
I have a very deep nested tree structure Json and I am given an object that can exist at any depth. I need to be able to iterate through all grandparent / parent / children nodes until I find the requested category, plus be able to capture its grandparent / parent / children categories all the way through.
//input data structure
[{
"Type": "grdparent1",
"name": "grdparent1",
"children": [{
"Type": "grdparent1",
"Id": 45,
"children": []
}, {
"Type": "grdparent1",
"Id": 46,
"children": [{
"Type": "parent1",
"Id": 54,
"children": [{
"Type": "child1",
"Id": 63,
"children": []
}, {
"Type": "child2",
"Id": 64,
"children": []
}]
}, {
"Type": "parent2",
"Id": 57,
"children": []
}]
}]
}, {
"Type": "grdparent2",
"name": "grdparent2",
"children": [{
"Type": "grdparent2",
"Id": 4,
"children": [{
"Type": "parent1",
"Id": 16,
"children": [{
"children": [],
"Type": "child1",
"Id": 28,
}]
}, {
"Type": "parent2",
"Id": 17,
"children": []
}]
}]
}, {
"Type": "grdparent3",
"name": "grdparent3",
"children": []
}, {
"Type": "grdparent4",
"name": "grdparent4",
"children": [{
"Type": "parent1",
"Id": 167,
"children": []
}]
}]
//output
[{
"grdparent1": [{
"Id": 45,
}, {
"Id": 46,
"parent1": [{
"Id": 54,
"child1": {
"Id": 63
}
}, {
"child2": {
"Id": 64
}
}]
}, {
"parent2": [{
"Id": 57
}]
}]
}, {
"grdparent2": [{
"Id": 4,
"parent1": [{
"Id": 16,
"child1": [{
"Id": 28
}]
}, {
"parent2": [{
"Id": 17
}]
}]
}, {
"grdparent4": [{
"parent1": [{
"Id": 167
}]
}]
}]
}]
Here is the code. You have the result in output variable:
var input = [{
"Type": "grdparent1",
"name": "grdparent1",
"children": [{
"Type": "grdparent1",
"Id": 45,
"children": []
}, {
"Type": "grdparent1",
"Id": 46,
"children": [{
"Type": "parent1",
"Id": 54,
"children": [{
"Type": "child1",
"Id": 63,
"children": []
}, {
"Type": "child2",
"Id": 64,
"children": []
}]
}, {
"Type": "parent2",
"Id": 57,
"children": []
}]
}]
}, {
"Type": "grdparent2",
"name": "grdparent2",
"children": [{
"Type": "grdparent2",
"Id": 4,
"children": [{
"Type": "parent1",
"Id": 16,
"children": [{
"children": [],
"Type": "child1",
"Id": 28,
}]
}, {
"Type": "parent2",
"Id": 17,
"children": []
}]
}]
}, {
"Type": "grdparent3",
"name": "grdparent3",
"children": []
}, {
"Type": "grdparent4",
"name": "grdparent4",
"children": [{
"Type": "parent1",
"Id": 167,
"children": []
}]
}];
var output = [];
for(var index = 0; index < input.length; index++) {
if(input[index].children.length > 0) {
var parsedObject = parseTopLevelItem(input[index]);
if(parsedObject) {
output[output.length] = parsedObject;
}
}
}
alert(JSON.stringify(output));
function parseTopLevelItem(item) {
var topLevelReturnObject;
if(item.children.length > 0) {
topLevelReturnObject = {};
for(var i = 0; i < item.children.length; i++) {
var parsedObject = parseChild(item.children[i]);
if(parsedObject) {
var key = parsedObject[0];
if(!topLevelReturnObject[key]) {
topLevelReturnObject[key] = [];
}
topLevelReturnObject[key][(topLevelReturnObject[key]).length] = parsedObject[1];
}
}
}
return topLevelReturnObject;
}
function parseChild(childElement){
var returnObject = [];
returnObject[0] = childElement.Type;
returnObject[1] = {};
returnObject[1].Id = childElement.Id;
for(var i = 0; i < childElement.children.length; i++) {
var parsedObject = parseChild(childElement.children[i]);
if(parsedObject) {
var key = parsedObject[0];
if(!returnObject[1][key]) {
returnObject[1][key] = [];
}
returnObject[1][key][(returnObject[1][key]).length] = parsedObject[1];
}
}
return returnObject;
}
As this has been brought back up...
Here's an approach which uses some fairly standard utility functions to allow for a fairly simple implementation of your node reformatting and wraps that in a simple transformation of your whole forest of nodes. (It's not a tree, precisely, as there isn't a single root. This is commonly called a "forest".)
// utility functions
const groupBy = (fn) => (xs) =>
xs .reduce ((a, x) => ({... a, [fn(x)]: [... (a [fn (x)] || []), x]}), {})
const mapObject = (fn) => (obj) =>
Object .fromEntries (Object .entries (obj) .map (([k, v]) => [k, fn(v)]))
// helper functions
const hasKids = ({children = []}) => children .length > 0
const hasGrandkids = ({children = []}) => children .some (hasKids)
// main functions
const reformat = ({Id, children = []}) => ({
...(Id ? {Id} : {}),
... mapObject (kids => kids .map (reformat)) (groupBy (o => o.Type) (children))
})
const transform = (nodes) =>
nodes .filter (hasGrandkids) .map (reformat)
// sample data
const input = [{Type: "grdparent1", name: "grdparent1", children: [{Type: "grdparent1", Id: 45, children: []}, {Type: "grdparent1", Id: 46, children: [{Type: "parent1", Id: 54, children: [{Type: "child1", Id: 63, children: []}, {Type: "child2", Id: 64, children: []}]}, {Type: "parent2", Id: 57, children: []}]}]}, {Type: "grdparent2", name: "grdparent2", children: [{Type: "grdparent2", Id: 4, children: [{Type: "parent1", Id: 16, children: [{children: [], Type: "child1", Id: 28}]}, {Type: "parent2", Id: 17, children: []}]}]}, {Type: "grdparent3", name: "grdparent3", children: []}, {Type: "grdparent4", name: "grdparent4", children: [{Type: "parent1", Id: 167, children: []}]}]
// demo
console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
The two important utility functions used are
groupBy which groups an array into an object where the keys are generated by the supplied function mapped against the elements, and the values are arrays of the elements that generated that key. That is, for example,
groupBy (({name}) => name[0]) ([
{name: 'alice', age: 26},
{name: 'bob', age: 19},
{name: 'andrew', age: 31},
{name: 'carol', age: 22}
])
//=>
// {
// a: [{name: 'alice', age: 26}, {name: 'andrew', age: 31}],
// b: [{name: 'bob', age: 19}],
// c: [{name: 'carol', age: 22}]
// }
and mapObject, which maps a function over the values of an object, for example,
mapObject (n => n * n) ({a: 2, b: 3, c: 5, d: 7})
//=> {a: 1, b: 4, c: 25, d: 49}
We also have two helper functions which simply determine whether the node has children and whether it has grandchildren. We will use this in transform, to choose only those nodes with grandchildren. And that, in fact, is all transform does: it filters the list of nodes to include only those with grandchildren, and then it calls our reformat function on each of them. (It's not at all clear to me that this is what was desired in the first place. The question title and text refer to searching for nodes, but there is no evidence in any code of actual searching taking place. I'm guessing here in a way that matches the sample output and at least one other answer. This part would be easy enough to refactor.)
reformat is the main function here, formatting a node and recurring on each of its children. This is a fairly tricky reformat, turning child Type names into object keys and including Id in the output only when it's present in the input.
But the code isn't that complex, thanks to the use of the two helper functions. We group the children by their Type property, and then on the resulting object, we use mapObject to apply kids => kids .map (reformat) to each node.
groupBy and mapObject are general-purpose utility functions, and there are equivalents in major libraries like Underscore, Lodash, and Ramda (disclaimer: I'm a Ramda principal team member). But these implementation show that it's fairly easy to maintain your own versions.
This really was an interesting question! Took me a while to figure it out :)
I'm not a big fan of reinventing the wheel. So I'd suggest you use a library. We like object-scan for data processing since it is very powerful once you wrap your head around it. Having said that, this question was tricky! Here is how you could solve it
// const objectScan = require('object-scan');
const data = [{ Type: 'grdparent1', name: 'grdparent1', children: [{ Type: 'grdparent1', Id: 45, children: [] }, { Type: 'grdparent1', Id: 46, children: [{ Type: 'parent1', Id: 54, children: [{ Type: 'child1', Id: 63, children: [] }, { Type: 'child2', Id: 64, children: [] }] }, { Type: 'parent2', Id: 57, children: [] }] }] }, { Type: 'grdparent2', name: 'grdparent2', children: [{ Type: 'grdparent2', Id: 4, children: [{ Type: 'parent1', Id: 16, children: [{ children: [], Type: 'child1', Id: 28 }] }, { Type: 'parent2', Id: 17, children: [] }] }] }, { Type: 'grdparent3', name: 'grdparent3', children: [] }, { Type: 'grdparent4', name: 'grdparent4', children: [{ Type: 'parent1', Id: 167, children: [] }] }];
const convert = (input) => {
objectScan(['**[*]'], {
breakFn: ({ isMatch, key, value, context }) => {
if (isMatch) {
context[key.length] = value.children.map(({ Type }) => Type);
}
},
filterFn: ({ key, value, parent, property, context }) => {
const result = 'Id' in value ? { Id: value.Id } : {};
context[key.length].forEach((type, idx) => {
result[type] = (result[type] || []).concat(value.children[idx]);
});
if (Object.keys(result).length === 0) {
parent.splice(property, 1);
} else {
parent.splice(property, 1, result);
}
}
})(input, []);
};
convert(data);
console.log(data);
// => [ { grdparent1: [ { Id: 45 }, { Id: 46, parent1: [ { Id: 54, child1: [ { Id: 63 } ], child2: [ { Id: 64 } ] } ], parent2: [ { Id: 57 } ] } ] }, { grdparent2: [ { Id: 4, parent1: [ { Id: 16, child1: [ { Id: 28 } ] } ], parent2: [ { Id: 17 } ] } ] }, { parent1: [ { Id: 167 } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
Note that the result matches the result of the currently accepted answer.

Categories

Resources