Creating nested object for a following Javascript object? - javascript

I want to build an object similar to following the structure:
record 1 {id,name,desc}
record 2 {id,name,desc}
record 3 {id,name,desc}
record 4 {id,name,desc}
Its a nested structure where the record 1 is the parent and the record 2 is the child of record 1, record 3 is the child of record 2, and so on.
I have gone through various posts in stack overflow, which suggests using methods like push, put etc which I am unable to generate correctly. Please help.
Goal
{
"8b9e235c0fe412004e938fbce1050e0f": [
{
"name": "Parent 1",
"childs": [
"caf23c95db3110100cc4bd513996195d": {
"name": "Child of Parent 1"
"childs": [
"caf23c95db3110100cc4bd513996195d": {
"name": "Child of Child 2"
}
]
}
]
}
]
}
Not sure if its the right order way but the idea is to turn the following object into a nested one:
[
{
"name": "Level 2",
"childId": "caf23c95db3110100cc4bd513996195d",
"parentId": "8b9e235c0fe412004e938fbce1050e0f",
"description": null,
"level": 1
},
{
"name": "Level 3",
"childId": "c303f495db3110100cc4bd51399619b8",
"parentId": "caf23c95db3110100cc4bd513996195d",
"description": null,
"level": 2
},
{
"name": "Level 4",
"childId": "be133895db3110100cc4bd51399619ba",
"parentId": "c303f495db3110100cc4bd51399619b8",
"description": null,
"level": 3
},
{
"name": "Intrusion and Incident Response Standard Operating Procedure",
"id": "8b9e235c0fe412004e938fbce1050e0f",
"description": "blablalblablabab",
"level": 0
}
]
Using this code..
function hasChild(parent,level){
var grProcessChild = new GlideRecord('sn_compliance_policy');
grProcessChild.addQuery('parent', parent);
grProcessChild.query();
while(grProcessChild.next()){
var level = parseInt(level) + 1;
var child = {}; // object
child.name = grProcessChild.getValue('name');
child.childId = grProcessChild.getUniqueValue();
child.parentId = grProcessChild.getValue('parent');
child.description = grProcessChild.getValue('description');
child.level = level;
arrData.push(child);
hasChild(grProcessChild.getUniqueValue(),level);
}
}
var arrData = []; // array
var grProcess = new GlideRecord('sn_compliance_policy');
grProcess.addQuery('sys_id','8b9e235c0fe412004e938fbce1050e0f');
grProcess.query();
while(grProcess.next()){
var root = {}; // object
root.name = grProcess.getValue('name');
root.id = grProcess.getUniqueValue();
root.description = grProcess.getValue('description');
root.level = 0;
hasChild(grProcess.getUniqueValue(),root.level);
arrData.push(root);
}

If you are using lodash Lodash in that _.reduceRight method will be helpful.
Here is a JSFiddle proving that it's working.

Related

How to filter an objects where is employeeId equals to some value

I'm using knockoutjs, but the question is really in Javascript domain.
I have variable vm.filteredSerivces() which contains all services by all employees.
Now, I want to just preserve those filteredSerivces where is vm.filteredSerivces()[0].GroupedServices[x].EmployeeId == 3684 (x is the number of index number of each object in GroupedServices object list)
I tried as follows:
var filteredSrvcs = vm.filteredSerivces()[0].GroupedServices.filter(x => x.EmployeeId != Id).remove();
vm.filteredSerivces(filteredSrvcs );
But I changed structure in that way, and my bindings in html is not corresponding.
Is there any other way to just remove this sub-sub object, and to preserve a structure as it is?
Here is the
Here's an example that maps a new array of new objects and the filter is set to only include the GroupedServices items where Id == 2000
let res = data.map(({ServiceTypeName, GroupedServices}) =>{
GroupedServices= GroupedServices.filter(({Id}) => Id == 2000);
return {ServiceTypeName,GroupedServices }
})
console.log(res)
<script>
let data =
[
{
"ServiceTypeName": "Type 1",
"GroupedServices": [{
"Id": 1,
"Name": "A"
}, {
"Id": 2,
"Name": "A"
},
{
"Id": 28456,
"Name": "AGSADGJS"
}]
},
{
"ServiceTypeName": "Type 2",
"GroupedServices": [{
"Id": 1203,
"Name": "AHASJ"
}, {
"Id": 2000,
"Name": "AHSJD"
},
{
"Id": 284536,
"Name": "UEHNCK"
}]
}];
</script>

Filtering and storing and finding parent child relationship in elements of array [duplicate]

This question already has answers here:
How to find a node in a tree with JavaScript
(19 answers)
Javascript - Find path to object reference in nested object
(3 answers)
Closed 1 year ago.
I am working on an angular application. My data is as follows
data= [
{
"id": 2,
"name": "Jamie",
"objectId": 200,
"parentId": null,
"children": [
{
"id": 98,
"name": "Rose",
"objectId": 100,
"parentId": 200,
"children": [
{
"id": 1212,
"name": "julie",
"objectId": 500,
"parentId": 100,
"children": []
}
]
},
{
"id": 67,
"name": "Kosy",
"objectId": 700,
"parentId": 200,
"children": []
}
]
}
]
I will be having input id and name. Suppose in my method I get id as 1212 and name as "julie". So I want to go to that node whose id is 1212 and name is equal to
"julie", once this condition is met. I need to check parentId in children is equal to objectId in parent till parentId becomes null.
If parent id becomes null then it is considered as last node and then I want to have my data in the array in following format. For id 1212 and name
"julie" resultArray is
resultArray = ["Jamie/Rose/Julie "]. Data starting from parent to children separated by slash.
Another example is if I get id as 67 and name "Kosy". then result array will be
resultArray = ["Jamie/Kosy"]
As parentId of Kosy is 200 and ObjectId of Jamie is 200, which indicate that Jamie is parent of Kosy, that's why data should look like this. want to have dynamic code as at run time data can be huge but structure and logic will be same as mentioned above
How can I do this
I wrote the code for this problem. Please try the following code. This is one of the typical tree-search problem. One point what sets it apart from traditional tree search is checking its parent. That was easily solved here.
const data = [
{
"id": 2,
"name": "Jamie",
"objectId": 200,
"parentId": null,
"children": [
{
"id": 98,
"name": "Rose",
"objectId": 100,
"parentId": 200,
"children": [
{
"id": 1212,
"name": "julie",
"objectId": 500,
"parentId": 100,
"children": []
}
]
},
{
"id": 67,
"name": "Kosy",
"objectId": 700,
"parentId": 200,
"children": []
}
]
}
];
// function that search the target in a node(not a array)
// In order for this function to return true, id comparison from the final node to this node should be passed as well
const findInNode = (node, id, name, output) => {
if (node.name === name && node.id === id) { // self test
return true;
} else {
const children = node.children;
if (!children) return false;
// find in children
for (let child of children) {
// output.paths is the current search path
output.paths.push(child.name);
if (findInNode(child, id, name, output)) {
// if found, compare the self objectId and child's parentId
return child.parentId === node.objectId;
}
output.paths.pop();
}
}
}
// function that search the target in an array, for use at the top-most level
const findInArray = (nodes, id, name, output) => {
for (let node of nodes) {
output.paths.push(node.name);
if (findInNode(node, id, name, output)) {
if (!node.parentId) {
output.found = true;
break;
}
}
output.paths.pop();
}
}
output = { paths: [], found: false };
findInArray(data, 1212, 'julie', output);
console.log(output.found);
console.log(output.paths.join('/'));

Remove childless elements (except leafs) from nested array

I've got a table called mappings with id, leaf, parent_id, name and flip_parent columns. parent_id and flip_parent are both integers referring to the mapping.id column. flip_parent holds an id of a value that should be excluded from the tree. To do so I've got the following function ($mappings are all rows from the mappings table, flipParentIds are all values from the same table where the flip_parent value is not null)
private function removeFlipParents(array $mappings, array $flipParentIds)
{
foreach ($mappings as $key => $mapping) {
foreach ($flipParentIds as $id) {
if ($mapping['id'] === $id['flipParent']) {
unset($mappings[$key]);
}
}
}
return $mappings;
}
After those values have been removed, I need to build a tree with the remaining data (the tree goes 5/6 levels deep), which is done with the following piece of code;
private function buildTree(array $elements, $parentId)
{
$branch = [];
foreach ($elements as $element) {
if ($element['parentId'] == $parentId) {
$children = $this->buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
} else {
$element['children'] = [];
}
}
}
return $branch;
}
In this case elements is the same array as $mappings, but without those flip parents. The result of this function is returned as a JSON response and handled by Javascript to build a tree. The returned JSON has a structure similar to this;
[{
"id": 1, "name": "Node 1", "children": [{
"id": 2, "name": "Node 1.1", "children": [{
"id": 4, "name": "Node 1.1.1", "leaf": true, "children": [], "gls": [{
"id": 1000, "name": "GL1", "code": "0100"
}, {
"id": 1001, "name": "GL2", "code": "0200"
}]
}, {
"id": 5, "name": "Node 1.1.2", "leaf": true, "children": [], "gls": [{
"id": 2000, "name": "GL3", "code": "0300"
}, {
"id": 2001, "name": "GL4", "code": "0400"
}]
}]
}, {
"id": 3, "name": "Node 1.2", "children": [{
"id": 6, "name": "Node 1.2.1", "leaf": true, "children": [], "gls": [{
"id": 3000, "name": "GL5", "code": "0500"
}, {
"id": 3001, "name": "GL6", "code": "0600"
}]
}]
}]
},
{
"id": 7, "name": "Node 2", "children": [{
"id": 8, "name": "Node 2.1", "children": [{
"id": 9, "name": "Node 2.1.1", "leaf": true, "children": [], "gls": [{
"id": 4000, "name": "GL7", "code": "0700"
}, {
"id": 4001, "name": "GL8", "code": "0800"
}]
}]
}]
}
]
To build the tree in JS I've got the following two functions
parseNodes(nodes)
{
const ul = document.createElement('ul');
ul.style.listStyleType = 'none';
nodes.forEach((node) => {
const parsedNode = this.parseNode(node);
if (parsedNode) {
ul.appendChild(parsedNode);
}
});
return ul;
}
parseNode(node)
{
if (node.name && node.name !== '') {
const li = document.createElement('li');
li.className = node.leaf ? 'leaf' : 'parent';
li.setAttribute('data-mapping-id', node.id);
if (node.parentId) {
li.setAttribute('data-parent-id', node.parentId);
}
li.innerHTML = node.name;
if (node.children) {
li.appendChild(this.parseNodes(node.children));
}
return li;
}
return null;
}
This all works perfectly fine, but I now want to remove childless, non-leaf nodes from the tree, as only the leafs will actually be usable. I don't know at what level the childless nodes are, and not all childless nodes are at the same level.
To do so I've written a recursive PHP function, which is called after removeFlipParents and buildTree (this needs to be done as I need the tree structure to check whether branches have children of not);
private function removeChildlessBranches(array $nodes)
{
foreach ($nodes as $key => $node) {
if (empty($node['children']) && !$node['leaf']) {
unset($nodes[$key]);
} else {
$this->removeChildlessBranches($node['children']);
}
}
return $nodes;
}
This kinda does what I want it to do as it removes all childless root nodes that aren't leafs, but it doesn't go to the 2nd level and beyond. It also changes the structure of $nodes. Without the removeChildlessBranches call the JSON is wrapped in an array ([{"id": 1, "name": "Node 1", ...}]) and I can iterate over it in the parseNodes function, but if I do use the removeChildlessBranches call, $nodes no longer is wrapped in an array ({"id": 1, "name": "Node 1", ...}) and can't be iterated over.
So my two questions are, how can I make removeChildlessBranches actually recursive, and how can I make it so it doesn't change the structure of $nodes?
You're passing in a copy of the $node['children'] array on this line $this->removeChildlessBranches($node['children']); (If you haven't heard of it before lookup pass by reference and pass by value). So any subsequent changes are being made to that copy and not your original array (which is also a copy). The results of the changes are then being thrown away as you're not doing anything with them.
You could fix that issue by changing the line to this $nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);.
Be aware however, that you may now have a node which has no children and is not a leaf but will not be removed correctly as you've already trimmed at that level. Trimming out children first and then unsetting should give you the desired result:
private function removeChildlessBranches(array $nodes)
{
foreach ($nodes as $key => $node) {
$nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);
if (empty($nodes[$key]['children']) && !$nodes[$key]['leaf']) {
unset($nodes[$key]);
}
}
return $nodes;
}

How to delete object from an array of objects having relations with each arrays?

This Object have relationship as: childOne > childTwo > childThree > childFour > childFive > childSix.
{
"parentObj": {
"childOne": [
{
"name": "A",
"id": "1"
},
{
"name": "B",
"id": "2"
}
],
"childTwo": [
{
"name": "AB",
"parent_id": "1",
"id": "11"
},
{
"name": "DE",
"parent_id": "2",
"id": "22"
}
],
"childThree": [
{
"name": "ABC",
"parent_id": "22",
"id": "111"
},
{
"name": "DEF",
"parent_id": "11",
"id": "222"
}
],
"childFour": [
{
"name": "ABCD",
"parent_id": "111",
"id": "1111"
},
{
"name": "PQRS",
"parent_id": "111",
"id": "2222"
}
],
"childFive": [
{
"name": "FGRGF",
"parent_id": "1111",
"id": "11111"
},
{
"name": "ASLNJ",
"parent_id": "1111",
"id": "22222"
},
{
"name": "ASKJA",
"parent_id": "1111",
"id": "33333"
}
],
"childSix": [
{
"name": "SDKJBS",
"parent_id": "11111",
"id": "111111"
},
{
"name": "ASKLJB",
"parent_id": "11111",
"id": "222222"
}
]
}
}
Is there any way to delete an item by ID and the objects which are associated with that particular ID should get deleted(i.e., If I do delete parentObj.childTwo[1], then all the related object beneath it should also gets deleted).
Looping manually is too bad code, and generate bugs. There must be better ways of dealing with this kind of problems like recursion, or other.
The data structure does not allow for efficient manipulation:
By nature objects have an non-ordered set of properties, so there is no guarantee that iterating the properties of parentObj will give you the order childOne, childTwo, childThree, ... In practice this order is determined by the order in which these properties were created, but there is no documented guarantee for that. So one might find children before parents and vice versa.
Although the id values within one such child array are supposed to be unique, this object structure does not guarantee that. Moreover, given a certain id value, it is not possible to find the corresponding object in constant time.
Given this structure, it seems best to first add a hash to solve the above mentioned disadvantages. An object for knowing a node's group (by id) and an object to know which is the next level's group name, can help out for that.
The above two tasks can be executed in O(n) time, where n is the number of nodes.
Here is the ES5-compatible code (since you mentioned in comments not to have ES6 support). It provides one example call where node with id "1111" is removed from your example data, and prints the resulting object.
function removeSubTree(data, id) {
var groupOf = {}, groupAfter = {}, group, parents, keep = { false: [], true: [] };
// Provide link to group per node ID
for (group in data) {
data[group].forEach(function (node) {
groupOf[node.id] = group;
});
}
// Create ordered sequence of groups, since object properties are not ordered
for (group in data) {
if (!data[group].length || !data[group][0].parent_id) continue;
groupAfter[groupOf[data[group][0].parent_id]] = group;
}
// Check if given id exists:
group = groupOf[id];
if (!group) return; // Nothing to do
// Maintain list of nodes to keep and not to keep within the group
data[group].forEach(function (node) {
keep[node.id !== id].push(node);
});
while (keep.false.length) { // While there is something to delete
data[group] = keep.true; // Delete the nodes from the group
if (!keep.true.length) delete data[group]; // Delete the group if empty
// Collect the ids of the removed nodes
parents = {};
keep.false.forEach(function (node) {
parents[node.id] = true;
});
group = groupAfter[group]; // Go to next group
if (!group) break; // No more groups
// Determine what to keep/remove in that group
keep = { false: [], true: [] };
data[group].forEach(function (node) {
keep[!parents[node.parent_id]].push(node);
});
}
}
var tree = {"parentObj": {"childOne": [{"name": "A","id": "1"},{"name": "B","id": "2"}],"childTwo": [{"name": "AB","parent_id": "1","id": "11"},{"name": "DE","parent_id": "2","id": "22"}],"childThree": [{"name": "ABC","parent_id": "22","id": "111"},{"name": "DEF","parent_id": "11","id": "222"}],"childFour": [{"name": "ABCD","parent_id": "111","id": "1111"},{"name": "PQRS","parent_id": "111","id": "2222"}],"childFive": [{"name": "FGRGF","parent_id": "1111","id": "11111"},{"name": "ASLNJ","parent_id": "1111","id": "22222"},{"name": "ASKJA","parent_id": "1111","id": "33333"}],"childSix": [{"name": "SDKJBS","parent_id": "11111","id": "111111"},{"name": "ASKLJB","parent_id": "11111","id": "222222"}]}}
removeSubTree(tree.parentObj, "1111");
console.log(tree.parentObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Sure, the function you use to delete an entry should FIRST recurse, which means run itself on the linked entry, unless there is none. So, in psuedocode
function del(name, index)
{
if parent[name][index] has reference
Then del(reference name, reference ID)
Now del parent[name][index]
}
No loop needed.
And since we stop if there is no reference, we do not recurse forever.
Not sure what it is you want but maybe this will work:
const someObject = {
"parentObj": {
"childOne": [
{
"name": "A",
"id": "1"
},
{
"name": "B",
"id": "2"
}
],
"childTwo": [
{
"name": "AB",
"childOne": "1",
"id": "11"
},
{
"name": "DE",
"childOne": "2",
"id": "22"
}
]
}
};
const removeByID = (key,id,parent) =>
Object.keys(parent).reduce(
(o,k)=>{
o[k]=parent[k].filter(
item=>
!(Object.keys(item).includes(key)&&item[key]===id)
);
return o;
},
{}
);
const withoutID = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","1",someObject.parentObj) }
);
console.log(`notice that childTwo item with childOne:"1" is gone`);
console.log("without key:",JSON.stringify(withoutID,undefined,2));
const otherExample = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","2",someObject.parentObj) }
);
console.log(`notice that childTwo item with childOne:"2" is gone`);
console.log("without key:",JSON.stringify(otherExample,undefined,2));
const both = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","1",otherExample.parentObj) }
);
console.log(`notice that childTwo items with childOne are both gone`);
console.log("without key:",JSON.stringify(both,undefined,2));

Search deep nested

I am working on a solution where I need to search for an element in a deeply nested JSON by its id. I have been advised to use underscore.js which I am pretty new to.
After reading the documentation http://underscorejs.org/#find , I tried to implement the solution using find, filter and findWhere.
Here is what I tried using find :
var test = {
"menuInputRequestId": 1,
"catalog":[
{
"uid": 1,
"name": "Pizza",
"desc": "Italian cuisine",
"products": [
{
"uid": 3,
"name": "Devilled chicken",
"desc": "chicken pizza",
"prices":[
{
"uid": 7,
"name": "regular",
"price": "$10"
},
{
"uid": 8,
"name": "large",
"price": "$12"
}
]
}
]
},
{
"uid": 2,
"name": "Pasta",
"desc": "Italian cuisine pasta",
"products": [
{
"uid": 4,
"name": "Lasagne",
"desc": "chicken lasage",
"prices":[
{
"uid": 9,
"name": "small",
"price": "$10"
},
{
"uid": 10,
"name": "large",
"price": "$15"
}
]
},
{
"uid": 5,
"name": "Pasta",
"desc": "chicken pasta",
"prices":[
{
"uid": 11,
"name": "small",
"price": "$8"
},
{
"uid": 12,
"name": "large",
"price": "$12"
}
]
}
]
}
]
};
var x = _.find(test, function (item) {
return item.catalog && item.catalog.uid == 1;
});
And a Fiddle http://jsfiddle.net/8hmz0760/
The issue I faced is that these functions check the top level of the structure and not the nested properties thus returning undefined. I tried to use item.catalog && item.catalog.uid == 1; logic as suggested in a similar question Underscore.js - filtering in a nested Json but failed.
How can I find an item by value by searching the whole deeply nested structure?
EDIT:
The following code is the latest i tried. The issue in that is that it directly traverses to prices nested object and tries to find the value. But my requirement is to search for the value in all the layers of the JSON.
var x = _.filter(test, function(evt) {
return _.any(evt.items, function(itm){
return _.any(itm.outcomes, function(prc) {
return prc.uid === 1 ;
});
});
});
Here's a solution which creates an object where the keys are the uids:
var catalogues = test.catalog;
var products = _.flatten(_.pluck(catalogues, 'products'));
var prices = _.flatten(_.pluck(products, 'prices'));
var ids = _.reduce(catalogues.concat(products,prices), function(memo, value){
memo[value.uid] = value;
return memo;
}, {});
var itemWithUid2 = ids[2]
var itemWithUid12 = ids[12]
I dont use underscore.js but you can use this instead
function isArray(what) {
return Object.prototype.toString.call(what) === '[object Array]';
}
function find(json,key,value){
var result = [];
for (var property in json)
{
//console.log(property);
if (json.hasOwnProperty(property)) {
if( property == key && json[property] == value)
{
result.push(json);
}
if( isArray(json[property]))
{
for(var child in json[property])
{
//console.log(json[property][child]);
var res = find(json[property][child],key,value);
if(res.length >= 1 ){
result.push(res);}
}
}
}
}
return result;
}
console.log(find(test,"uid",4));

Categories

Resources