Create a tree structure from an array with parent-child references - javascript

I am trying to alter the json in snippet to a tree structure just like in https://www.primefaces.org/primeng/#/treetable (below is the sample i expect too). I understand it involves recursion but I ain't sure how to deeply link each.
The output i expect is something like below. The json whose parent is true becomes the root. If the root has values, the json corresponding to id of the value is pushed to children array with a json object "data". Again if that json has values, the json correspond to the id of value is pushed to children array with a json object "data and so on.
The code i have written is just a initial phase. Need help on how nesting can be done through iteration.
[
{
"data": {
"parent": true,
"id": "C001",
"type": "Folder",
"values": [
{
"id": "P001",
"type": "File"
}
]
},
"children": [
{
"data": {
"parent": false,
"id": "P001",
"type": "File",
"values": [
{
"id": "P002",
"type": "Image"
}
]
},
"children": [
{
"data": {
"parent": false,
"id": "P002",
"type": "Image",
"values": [
]
}
}
]
}
]
},
{
"data": {
"parent": true,
"id": "S000",
"type": "Something",
"values": [
]
}
}
]
var junkdata=[
{
"parent": false,
"id": "P001",
"type":"File",
"values": [
{
"id": "P002",
"type": "Image"
}
]
},
{
"parent": true,
"id": "C001",
"type": "Folder",
"values": [
{
"id": "P001",
"type": "File"
}]
},
{
"parent": false,
"id": "P002",
"type": "Image",
"values":[]
},
{
"parent": true,
"id": "S000",
"type": "Something",
"values":[]
}];
var parentDatas=junkdata.filter((x)=>x.parent==true);
if(parentDatas.length>0){
var finalResponse=parentDatas.map((parentData)=>{
var resultJson={};
resultJson.data=parentData;
if(parentData.values.length>0){
resultJson.children=[];
for(var i of parentData.values){
var child=junkdata.find((x)=>x.id==i.id);
if(child){
var jsonObj={};
jsonObj.data=child;
resultJson.children.push(jsonObj);
}
}
}
return resultJson;
})
}
console.log(JSON.stringify(finalResponse));

Basically, we can start with this to process the root nodes:
let tree = yourData.filter(x => x.parent).map(process);
where process is the recursive function that processes a given node:
let process = node => ({
id: node.id,
type: node.type,
children: node.values.map(x => process(
yourData.find(y => y.id === x.id)))
});
For each id in node.values, it locates a node with that id and recursively calls process on it. Once all child nodes are dealt with, process collects them into an array and returns the newly formatted object.
This is the general recursion pattern for working with graph-alike structures, where you have "nodes" somehow connected to other "nodes":
function F (N: node) {
for each node M which is connected to N {
F (M) <--- recursion
}
result = do something with N
return result
}

Related

Flatten an array of unknown deep objects, with named properties, in Async/Await JavaScript (or better TypeScript)

I've already seen that such questions have been answered 1000 times, but I can't get it.
I have an array with various unknown depth objects, only in the property children are/might be more objects of the same schema as the parent object.
I need all objects regardless of their dependency in a list.
Here is an example of the raw data:
[
{
"id": "root________",
"title": "",
"index": 0,
"dateAdded": 1607859798059,
"type": "folder",
"dateGroupModified": 1607859798494,
"children": [
{
"id": "menu________",
"title": "Lesezeichen-Menü",
"index": 0,
"dateAdded": 1607859798059,
"type": "folder",
"parentId": "root________",
"dateGroupModified": 1607859798427,
"children": [
{
"id": "vzj790Oc5ncn",
"title": "Mozilla Firefox",
"index": 0,
"dateAdded": 1607859798427,
"type": "folder",
"parentId": "menu________",
"dateGroupModified": 1607859798427,
"children": [
{
"id": "YIjZdOQ4I3nz",
"title": "Hilfe und Anleitungen",
"index": 0,
"dateAdded": 1607859798427,
"type": "bookmark",
"url": "https://support.mozilla.org/de/products/firefox",
"parentId": "vzj790Oc5ncn"
},
{
"id": "cHfBIRuk3-d0",
"title": "Firefox anpassen",
"index": 1,
"dateAdded": 1607859798427,
"type": "bookmark",
"url": "https://support.mozilla.org/de/kb/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize",
"parentId": "vzj790Oc5ncn"
},
{
"id": "wBPLt_b_UKWN",
"title": "Machen Sie mit",
"index": 2,
"dateAdded": 1607859798427,
"type": "bookmark",
"url": "https://www.mozilla.org/de/contribute/",
"parentId": "vzj790Oc5ncn"
},
{
"id": "LngszJqD2COI",
"title": "Über uns",
"index": 3,
"dateAdded": 1607859798427,
"type": "bookmark",
"url": "https://www.mozilla.org/de/about/",
"parentId": "vzj790Oc5ncn"
}
]
}
]
},
{
"id": "toolbar_____",
"title": "Lesezeichen-Symbolleiste",
"index": 1,
"dateAdded": 1607859798059,
"type": "folder",
"parentId": "root________",
"dateGroupModified": 1607859798494,
"children": [
{
"id": "DnLPkDUWf4k7",
"title": "Erste Schritte",
"index": 0,
"dateAdded": 1607859798494,
"type": "bookmark",
"url": "https://www.mozilla.org/de/firefox/central/",
"parentId": "toolbar_____"
}
]
},
{
"id": "unfiled_____",
"title": "Weitere Lesezeichen",
"index": 3,
"dateAdded": 1607859798059,
"type": "folder",
"parentId": "root________",
"dateGroupModified": 1607859798407,
"children": []
},
{
"id": "mobile______",
"title": "Mobile Lesezeichen",
"index": 4,
"dateAdded": 1607859798081,
"type": "folder",
"parentId": "root________",
"dateGroupModified": 1607859798407,
"children": []
}
]
}
]
I have written a repeating function that goes through the nested elements recursively, however within the repeater function it is not being called again despite the repeat call.
How do I call the custom async function again in TypeScript? recursively
repeater = async (node: any): Promise<any> => {
const summeryRepeater : any[] = [];
const children = await this.getChildren(node.id);
if (children.length >= 1) {
for (const node of children) {
summeryRepeater.push(node)
await this.repeater(node)
}
}
return summeryRepeater
}
// TODO
// eslint-disable-next-line #typescript-eslint/no-explicit-any
getTreeList = async (): Promise<any> => {
const summery : any[] = [];
const node = await browser.bookmarks.get("root________"); // return non children
summery.push(node[0])
const children = await this.getChildren(node[0].id); // return non children
if (children.length >= 1) {
for (const node of children) {
summery.push(node)
// // Repeat start
const repeater = await this.repeater(node)
for (const nodeR of repeater) {
summery.push(nodeR)
}
// // Repeat end
}
}
return summery
}
I have been stuck here for days and really need your help. Can anyone give me a hint?
THX, John
Firstly, a task like this doesn't really need async/await or promises - there's nothing asynchronous about flattening a nested data structure.
It's hard to work with the code you posted as its incomplete, so I just made a code sample from scratch that'll take your nested structure and return a flattened version.
const flattenStructure = entries => (
entries
.flatMap(entry => [entry, ...flattenStructure(entry.children || [])])
.flat()
)
If you call flattenStructure() on your example data above, you'll see that it produces a list of all the objects contained in your structure. It does not modify the objects, so some objects would still have the arrayOfObjects attribute on them that list what their descendants are.
References of functions/concepts used in that code snippet:
array.flatMap() which is the same as array.map() followed by array.flat().
spread syntax (this thing: "...")

Search for matches in an array of objects. JS

I have the following response from the server. I need to search this answer and compare it in turn with each field.
Example:
My task is that I know for sure that there should be 3 objects and each object has its own value for the type field, this is 'API', 'DEFAULT' or 'X'. How can you make it so that you can search for these three values ​​in the whole object and get an error if one of them is missing?
{
"result": [
{
"id": "54270522",
"key": "1-16UUC93PT",
"type": "API"
},
{
"id": "54270522",
"key": "3-1JOPPEIZI",
"type": "DEFAULT"
},
{
"id": "54270522",
"key": "3-1JOPPEIZI",
"type": "Х"
}
],
"success": true
}
You can first verify that the length is 3 and then loop over all the types and check if each one is present.
const data = {
"result": [
{
"id": "54270522",
"key": "1-16UUC93PT",
"type": "API"
},
{
"id": "54270522",
"key": "3-1JOPPEIZI",
"type": "DEFAULT"
},
{
"id": "54270522",
"key": "3-1JOPPEIZI",
"type": "Х"
}
],
"success": true
};
const requiredTypes = ['API', 'DEFAULT', 'Х'];
const types = new Set(data.result.map(({type})=>type));
const good = data.result.length === 3 && requiredTypes.every(type=>types.has(type));
console.log(good);
In case you would like to also know which value of those 3 are missing:
const check = (obj) => {
if (obj.result.length !== 3) return false;
let validTypes = ['API', 'DEFAULT', 'X'];
obj.result.forEach((r) => {
const index = validTypes.indexOf(r.type);
if (index !== -1) validTypes.splice(index, 1);
})
if (validTypes.length) return `${validTypes.join(', ')} is missing`;
return true;
};
So if you would have something like:
const test = {
"result": [
{
"id": "54270522",
"key": "1-16UUC93PT",
"type": "API"
},
{
"id": "54270522",
"key": "3-1JOPPEIZI",
"type": "DEFAULT"
},
{
"id": "54270522",
"key": "3-1JOPPEIZI",
"type": "X2"
}
],
"success": true
}
and you call check(test) it will return "X is missing". If all three types are present in the object that gets passed into the check function, it will return true. Of course this can be adjusted as you need. More objects, different types etc...

How to get property value of JSON Tree Schema using JavaScript?

I have a JSON Tree structure like this.
[
{
"title": "Blogs",
"id": "blogs",
"type": "array",
"children": [
{
"title": "Today",
"id": "today",
"type": "string"
},
{
"title": "Yesterday",
"id": "yesterday",
"type": "enum",
"options": [
"Y1",
"Y2"
]
}
]
},
{
"title": "Links",
"id": "links",
"type": "object",
"children": [
{
"title": "Oracle",
"id": "oracle",
"children": [
{
"title": "USA",
"id": "usa",
"type": "array",
"children": [
{
"title": "Midwest",
"id": "midwest",
"type": "enum",
"options": [
"Md1",
"Md2"
]
},
{
"title": "West",
"id": "west",
"type": "boolean"
}
]
},
{
"title": "Asia",
"id": "asia",
"type": "array",
"children": [
{
"title": "India",
"id": "india",
"type": "string"
}
]
}
]
}
]
}
]
I want a recursive function which takes 2 arguments(1st argument is The actual Tree Data and 2nd argument is a path with dot notation) and returns the type of the node (string/object/array/boolean) and enum values if the type is enum. The dot notation path may contain the array index as 0 or 1 or so on.
Basically what i want is
var nodeType = getType(treeData, 'links.oracle.usa.0.midwest'); // Note: there is a 0 as usa is an array type
console.log(nodeType); // Should return [{"type":"enum"},{"options": ["md1", "md2"]}]
var nodeType = getType(treeData, 'blogs.0.today');
console.log(nodeType); // Should return [{"type":"string"}]
Seems like working code, which handles wrong paths as well:
const sample = [
{
"title": "Blogs",
"id": "blogs",
"type": "array",
"children": [
{
"title": "Today",
"id": "today",
"type": "string"
},
{
"title": "Yesterday",
"id": "yesterday",
"type": "enum",
"options": [
"Y1",
"Y2"
]
}
]
},
{
"title": "Links",
"id": "links",
"type": "object",
"children": [
{
"title": "Oracle",
"id": "oracle",
"children": [
{
"title": "USA",
"id": "usa",
"type": "array",
"children": [
{
"title": "Midwest",
"id": "midwest",
"type": "enum",
"options": [
"Md1",
"Md2"
]
},
{
"title": "West",
"id": "west",
"type": "boolean"
}
]
},
{
"title": "Asia",
"id": "asia",
"type": "array",
"children": [
{
"title": "India",
"id": "india",
"type": "string"
}
]
}
]
}
]
}
]
const getType = (tree, path) => {
if (!path.length) return
const element = getElementFromTree(tree, path.split('.'))
if (!element || !element.type) return
const res = [{ type: element.type }]
if (element.options) {
res.push({ options: element.options })
}
return res
}
const getElementFromTree = (treePart, path) => {
const prop = path.shift()
if (!path.length) {
return treePart.id === prop ? treePart : undefined
}
let nextTreePart;
if (Array.isArray(treePart)) {
nextTreePart = treePart.find(v => v.id === prop)
} else if (isNaN(prop)) {
nextTreePart = treePart.children.find(v => v.id === prop)
} else {
nextTreePart = treePart.children[prop]
}
if (!nextTreePart) return
if (path.length) {
return getElementFromTree(nextTreePart, path)
}
return nextTreePart
}
// work as expected:
console.log(getType(sample, 'links.oracle.usa.0.midwest'))
console.log(getType(sample, 'links.oracle.usa.1.west'))
console.log(getType(sample, 'blogs.0.today'))
console.log(getType(sample, 'blogs.1.yesterday'))
console.log(getType(sample, 'links.oracle.asia.0.india'))
// tests with wrong paths, all return undefined
console.log(getType(sample, 'links.oracle.usa.5.west')) // because 5th element doesn't exists
console.log(getType(sample, 'blogs.3.today')) // because 3rd element doesn't exists
console.log(getType(sample, 'links.oracle')) // because links.oracle doesn't contain type field in it
console.log(getType(sample, '10.this.is.wrong.path')) // because path doesn't exist at all
Hope it helps <3
I would prefer to break this into a few functions. The tree-searching code starts with a path like ["links", "oracle", "usa", "midwest"] and a data object with a children array property, returning the node at that path, or undefined if it doesn't exist.
Then we write a simple wrapper to convert your "links.oracle.usa.0.midwest" string into that array and to wrap your input array into the children property of a new object. This getNode function also returns the node or undefined. It is an independently useful function.
Then because you eventually want the node type, we add the simple wrapper getType to report on the node's type or "unknown" if it's not found. We could easily replace "unknown" with undefined or whatever you choose in an obvious manner.
const findInTree = (xs) => ([p = undefined, ...ps]) =>
xs == undefined
? undefined
: p == undefined
? xs
: findInTree (xs .children .find (({id}) => id == p)) (ps)
const getNode = (xs) => (path) =>
findInTree ({children: xs}) (path .split ('.') .filter (isNaN))
const getType = (xs) => (path) =>
(getNode (xs) (path) || {type: 'unknown'}) .type
const data = [{title: "Blogs", id: "blogs", type: "array", children: [{title: "Today", id: "today", type: "string"}, {title: "Yesterday", id: "yesterday", type: "enum", options: ["Y1", "Y2"]}]}, {title: "Links", id: "links", type: "object", children: [{title: "Oracle", id: "oracle", children: [{title: "USA", id: "usa", type: "array", children: [{title: "Midwest", id: "midwest", type: "enum", options: ["Md1", "Md2"]}, {title: "West", id: "west", type: "boolean"}]}, {title: "Asia", id: "asia", type: "array", children: [{title: "India", id: "india", type: "string"}]}]}]}];
console .log (getType (data) ("links.oracle.usa.0.midwest")) //~> "enum"
console .log (getType (data) ("links.oracle.usa")) //~> "array"
console .log (getType (data) ("blogs.0.today")) //~> "string"
console .log (getType (data) ("blogs.2.tomorrow")) //~> "unknown"
These functions are all fairly simple. The recursion is clear; the breakdown of responsibilities should be straightforward.
But I had to make an assumption here. As pointed out in another answer, the array index and the following id are redundant. We could add complexity to the recursive function to deal with this case, but that would make for ugly code. Instead, before processing the node, we remove the array index. That's what the .filter (isNaN) is for in getNode. If this is not the desired behavior, if, for instance, you would want to fail or return undefined if the index and the id didn't match, then we'd have to do something pretty different. I didn't really follow your rationale for needed the index and the id, but in a comment on another answer you seem to imply that it's the id you really need. If it's both, then this technique would need heavy -- and ugly -- modification.
Use Lodash get method it allows for: _.get(object, 'a[0].b.c');. It is safe regarding getting value from non existing path - wont throw error.

Convert one Multidimensional JSON array to another

I have the following input object
{
"id": 1,
"isLeaf": false,
"name": "New rule",
"pid": 0,
"dragDisabled": true,
"children": [
{
"id": "new1",
"value": "data1",
"then": false,
"type": "set",
"forEach": false,
"pid": 1
},
{
"id": "new2",
"value": "data2",
"then": true,
"type": "if",
"forEach": false,
"pid": 1,
"children": [
{
"id": "new3",
"type": "Then",
"enableElse": true,
"pid": "new2",
"children": [
{
"id": "new5",
"value": "data3",
"then": false,
"type": "fuzzy_search",
"forEach": false,
"pid": "new3"
}
]
},
{
"id": "new4",
"type": "Else",
"enableElse": true,
"pid": "new2",
"children": [
{
"id": "new6",
"value": "data4",
"then": false,
"type": "return",
"forEach": false,
"pid": "new4"
}
]
}
]
}
]
}
I need to convert it into the following json
[
{
"id": "new1",
"condition": "data1"
},
{
"id": "new2",
"condition": "data2",
"then": [{
"id": "new5",
"condition": "data3"
}],
"else": [{
"id": "new6",
"condition": "data4"
}]
}
]
I have to recursively iterate through all existing inner child array of the input json array to formulate the output.
Following is the partially implemented code for the functionality.
ruleJSONFormatter = (request, parentItem, innerJSON) => {
try {
var outerObject = request;
if (outerObject.children && outerObject.children.length) {
var innerArray = outerObject.children;
// second iteration with inner children
innerArray.forEach((innerItem, index) => {
let parentObj = {};
let recursiveObj = {}; let thenArray = [];
recursiveObj['condition'] = innerItem.value && innerItem.value != undefined ? innerItem.value.formatedData : {};
recursiveObj['type'] = innerItem.type;
recursiveObj['id'] = innerItem.id;
recursiveObj['pid'] = innerItem.pid;
if (innerItem.children && innerItem.children != undefined && innerItem.children.length) {
switch (innerItem.type) {
case 'if':
recursiveObj['then'] = [];
recursiveObj['else'] = [];
}
if (Object.keys(parentObj).length == 0) {
parentObj = recursiveObj;
} else {
}
ruleJSONFormatter(innerItem, parentItem, parentObj)
} else {
if (Object.keys(parentObj).length == 0)
responseArray.push(innerJSON);
}
});
}
else {
console.log("No Values Inside the Formated Data ")
}
console.log("output-----------");
console.log(JSON.stringify(responseArray));
return responseArray
} catch (error) {
console.log('((((((((((((((((((((((((((', error)
}
}
final output array has a condition key which binds the value key from the input json and 'then' key which contains the multiple successive inner children array which is the success condition for type 'if' object. similar is the case for 'else' key in output
I find it hard to recursively call the same function to generate the desired output. the problem arises when there are deep nesting in the children array.Any help is appreciated.Thanks.

Most performant way to sort a deeply nested array of objects by another deeply nested array of objects

As an example - I've included a one element array that contains an object that has a Children key, which is an array of objects and each object also has its' own Children key that contains another array.
[
{
"Id": "1",
"Children": [
{
"Id": "2",
"Children": [
{
"Id": "10",
"DisplayName": "3-4",
},
{
"Id": "1000",
"DisplayName": "5-6",
},
{
"Id": "100",
"DisplayName": "1-2",
},
]
}
]
}
]
There is a second array of objects that I would like to compare the first array of objects to, with the intention of making sure that the first array is in the same order as the second array of objects, and if it is not - then sort until it is.
Here is the second array:
[
{
"Id": "1",
"Children": [
{
"Id": "2",
"Children": [
{
"Id": "100",
"DisplayName": "1-2",
},
{
"Id": "10",
"DisplayName": "3-4",
},
{
"Id": "1000",
"DisplayName": "5-6",
},
]
}
]
}
]
The data that this will run on can be up in the tens of thousands - so performance is paramount.
What I'm currently attempting is using a utility method to convert each element of the second array into a keyed object of objects e.g.
{
1: {
"Id": "1",
"Children": [
{
"Id": "2",
"Children": [
{
"Id": "4",
"DisplayName": "3-4",
},
{
"Id": "3",
"DisplayName": "1-2",
},
]
}
]
}
}
This allows fast look up from the top level. I'm wondering if I should continue doing this all the way down or if there is an idiomatic way to accomplish this. I considered recursion as well.
The order of the already sorted array is not based on Id - it is arbitrary. So the order needs to be preserved regardless.
Assuming same depth and all Id's exist in each level of each object use a recursive function that matches using Array#findIndex() in sort callback
function sortChildren(main, other) {
other.forEach((o, i) => {
if (o.children) {
const mChilds = main[i].children, oChilds = o.children;
oChilds.sort((a, b) => {
return mChilds.findIndex(main => main.Id === a.Id) - mChilds.findIndex(main => main.Id === b.Id)
});
// call function again on this level passing appropriate children arrays in
sortChildren(mChilds, oChilds)
}
})
}
sortChildren(data, newData);
console.log(JSON.stringify(newData, null, ' '))
<script>
var data = [{
"Id": "1",
"Children": [{
"Id": "2",
"Children": [{
"Id": "3",
"DisplayName": "1-2",
},
{
"Id": "4",
"DisplayName": "3-4",
},
]
}]
}]
var newData = [{
"Id": "1",
"Children": [{
"Id": "2",
"Children": [{
"Id": "4",
"DisplayName": "3-4",
},
{
"Id": "3",
"DisplayName": "1-2",
},
]
}]
}]
</script>

Categories

Resources