Javascript flatten deep nested children - javascript

I really can't figure out this one. I'm trying to flatten the category_id that are deep child of a specific node.
var categories = [{
"category_id": "66",
"parent_id": "59"
}, {
"category_id": "68",
"parent_id": "67",
}, {
"category_id": "69",
"parent_id": "59"
}, {
"category_id": "59",
"parent_id": "0",
}, {
"category_id": "67",
"parent_id": "66"
}, {
"category_id": "69",
"parent_id": "59"
}];
Or visually:
The closest I got to was to recursively loop through the first item found:
function children(category) {
var children = [];
var getChild = function(curr_id) {
// how can I handle all of the cats, and not only the first one?
return _.first(_.filter(categories, {
'parent_id': String(curr_id)
}));
};
var curr = category.category_id;
while (getChild(curr)) {
var child = getChild(curr).category_id;
children.push(child);
curr = child;
}
return children;
}
Current output of children(59) is ['66', '67', '68'].
Expected output is ['66', '67', '68', '69']

I didn't test but it should work:
function getChildren(id, categories) {
var children = [];
_.filter(categories, function(c) {
return c["parent_id"] === id;
}).forEach(function(c) {
children.push(c);
children = children.concat(getChildren(c.category_id, categories));
})
return children;
}
I am using lodash.
Edit: I tested it and now it should work. See the plunker: https://plnkr.co/edit/pmENXRl0yoNnTczfbEnT?p=preview
Here is a small optimisation you can make by discarding the filtered categories.
function getChildren(id, categories) {
var children = [];
var notMatching = [];
_.filter(categories, function(c) {
if(c["parent_id"] === id)
return true;
else
notMatching.push(c);
}).forEach(function(c) {
children.push(c);
children = children.concat(getChildren(c.category_id, notMatching));
})
return children;
}

Related

Find all the linked items to a particular item in an Object Array in JavaScript

I have an array of objects. You'll be able to find a sample of complete data here on JSONBlob.com.
Here's a simplified preview:
[
{
"name": "Unbetitelt",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [{
"i": "60a64930cf1db1710b97bf7a",
}],
"id": "60a6472ecf1db1710b97bf4c"
},
{
"name": "middleparent",
"parentCubo": "60a6472ecf1db1710b97bf4c",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [{
"i": "60a64936cf1db1710b97bf7d",
},
{
"i": "60a649afcf1db1710b97bfa6",
}
],
"id": "60a64930cf1db1710b97bf7a"
},
{
"name": "Unbetitelt",
"parentCubo": "60a64930cf1db1710b97bf7a",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [
{
"i": "60a6494acf1db1710b97bf8f",
},
{
"i": "60a64976cf1db1710b97bf9a",
}
],
"id": "60a64936cf1db1710b97bf7d"
},
{
"name": "Unbetitelt",
"parentCubo": "60a64930cf1db1710b97bf7a",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [],
"id": "60a649afcf1db1710b97bfa6"
},
{
"name": "Unbetitelt",
"parentCubo": "60a649c5cf1db1710b97bfb1",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [],
"id": "60a649efcf1db1710b97bfc4"
}
]
Each object in this array(the root array) has an id and childrenLayout property.
The childrenLayout property is an array(the child array) of Objects which will have an i property but won't have another childrenLayout. So in order to find children of each item in the childrenLayout array, we'll have to use this i field and find objects in the root array.
Once we find those objects in the root array, if the childrenLayout property on those objects is not an empty array, then we have to repeat the same process and find objects in this object's childrenLayout, thereby creating a list of all ids that are linked to one specific object with id x.
So, for instance, if we try to find all the items linked with id 60a6472ecf1db1710b97bf4c, we'll get:
60a64930cf1db1710b97bf7a
60a64936cf1db1710b97bf7d
60a649afcf1db1710b97bfa6
60a6494acf1db1710b97bf8f
60a64976cf1db1710b97bf9a
This is what I have so far and it's obviously not working as expcted and only returns the last three items from the expected items list mentioned above:
const findChildFor = item => {
if (item.childrenLayout.length > 0) {
const detailsOfItems = item.childrenLayout.map(({ i }) =>
data.find(datum => datum.id === i)
);
return detailsOfItems.map(itemDetails => findChildFor(itemDetails));
} else {
return item.id;
}
};
const [firstItem] = data;
// TODO: Work on a way to make it return children without having to flatten it.
const childrenRelatedToFirstItem = findChildFor(firstItem);
console.log(
'childrenRelatedToFirstItem: ',
childrenRelatedToFirstItem.flat(10)
);
To help speed up the solution, here's a Sample Stackblitz with the code so far.
Any help is greatly appreciated.
I found a solution. Code isn't so complex. Hoping this would be helpful.
const findChildFor = (arr, id, output) => {
const item = arr.find(obj => obj.id === id);
if (item) {
const childs = item.childrenLayout;
for (let child of childs) {
output.push(child.i);
findChildFor(arr, child.i, output);
}
}
}
output = [];
findChildFor(data, '60a6472ecf1db1710b97bf4c', output);
console.log(output);
This data looks like a graph description. Here is my solution for the problem with optimized time complexity and taking care of cyclic connections.
const findAllChildren = (arr, id) => {
// to find each child with O(1) access time
const familyMap = new Map(arr.map(it => [it.id, it.childrenLayout]));
// to check if a child has been collected with O(1) access time
const resultSet = new Set();
const findChildrenFor = (parentId) => {
familyMap.get(parentId).forEach(({i}) => {
// in a case of a cyclic graph
if (!resultSet.has(i)) {
resultSet.add(i);
findChildrenFor(i);
}
});
};
return Array.from(findChildrenFor(id));
};
I through in a solution in a different way but with the result that you want.
dataArray.reduce((acc,cur)=>{
acc[cur.id] = [];
cur.childrenLayout.map(child=>acc[cur.id].push(child.i))
Object.entries(acc).map(obj=>{
if(obj[1].includes(cur.id)){
cur.childrenLayout.map(child=>acc[obj[0]].push(child.i))
}
})
return acc;
},{})
Any question leave it in the comments section.
const dataArray = [
{
"name": "Unbetitelt",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [{
"i": "60a64930cf1db1710b97bf7a",
}],
"id": "60a6472ecf1db1710b97bf4c"
},
{
"name": "middleparent",
"parentCubo": "60a6472ecf1db1710b97bf4c",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [{
"i": "60a64936cf1db1710b97bf7d",
},
{
"i": "60a649afcf1db1710b97bfa6",
}
],
"id": "60a64930cf1db1710b97bf7a"
},
{
"name": "Unbetitelt",
"parentCubo": "60a64930cf1db1710b97bf7a",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [
{
"i": "60a6494acf1db1710b97bf8f",
},
{
"i": "60a64976cf1db1710b97bf9a",
}
],
"id": "60a64936cf1db1710b97bf7d"
},
{
"name": "Unbetitelt",
"parentCubo": "60a64930cf1db1710b97bf7a",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [],
"id": "60a649afcf1db1710b97bfa6"
},
{
"name": "Unbetitelt",
"parentCubo": "60a649c5cf1db1710b97bfb1",
"organizationMap": "60a55ed4e3a8973a02f910f1",
"childrenLayout": [],
"id": "60a649efcf1db1710b97bfc4"
}
]
const childFinder = arr => {
return arr.reduce((acc,cur)=>{
acc[cur.id] = [];
cur.childrenLayout.map(child=>acc[cur.id].push(child.i))
Object.entries(acc).map(obj=>{
if(obj[1].includes(cur.id)){
cur.childrenLayout.map(child=>acc[obj[0]].push(child.i))
}
})
return acc;
},{})
}
console.log(childFinder(dataArray))

Rename json keys iterative

I got a very simple json but in each block I got something like this.
var json = {
"name": "blabla"
"Children": [{
"name": "something"
"Children": [{ ..... }]
}
And so on. I don't know how many children there are inside each children recursively.
var keys = Object.keys(json);
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
var value = json[key];
delete json[key];
key = key.replace("Children", "children");
json[key] = value;
}
And now I want to replace all "Children" keys with lowercase "children". The following code only works for the first depth. How can I do this recursively?
It looks the input structure is pretty well-defined, so you could simply create a recursive function like this:
function transform(node) {
return {
name: node.name,
children: node.Children.map(transform)
};
}
var json = {
"name": "a",
"Children": [{
"name": "b",
"Children": [{
"name": "c",
"Children": []
}, {
"name": "d",
"Children": []
}]
}, {
"name": "e",
"Children": []
}]
};
console.log(transform(json));
A possible solution:
var s = JSON.stringify(json);
var t = s.replace(/"Children"/g, '"children"');
var newJson = JSON.parse(t);
Pros: This solution is very simple, being just three lines.
Cons: There is a potential unwanted side-effect, consider:
var json = {
"name": "blabla",
"Children": [{
"name": "something",
"Children": [{ ..... }]
}],
"favouriteWords": ["Children","Pets","Cakes"]
}
The solution replaces all instances of "Children", so the entry in the favouriteWords array would also be replaced, despite not being a property name. If there is no chance of the word appearing anywhere else other than as the property name, then this is not an issue, but worth raising just in case.
Here is a function that can do it recursivly:
function convertKey(obj) {
for (objKey in obj)
{
if (Array.isArray(obj[objKey])) {
convertKey[objKey].forEach(x => {
convertKey(x);
});
}
if (objKey === "Children") {
obj.children = obj.Children;
delete obj.Children;
}
}
}
And here is a more generic way for doing this:
function convertKey(obj, oldKey, newKey) {
for (objKey in obj)
{
if (Array.isArray(obj[objKey])) {
obj[objKey].forEach(objInArr => {
convertKey(objInArr);
});
}
if (objKey === oldKey) {
obj[newKey] = obj[oldKey];
delete obj[oldKey];
}
}
}
convertKey(json, "Children", "children");
Both the accepted answer, and #Tamas answer have slight issues.
With #Bardy's answer like he points out, there is the issue if any of your values's had the word Children it would cause problems.
With #Tamas, one issue is that any other properties apart from name & children get dropped. Also it assumes a Children property. And what if the children property is already children and not Children.
Using a slightly modified version of #Tamas, this should avoid the pitfalls.
function transform(node) {
if (node.Children) node.children = node.Children;
if (node.children) node.children = node.children.map(transform);
delete node.Children;
return node;
}
var json = {
"name": "a",
"Children": [{
"age": 13,
"name": "b",
"Children": [{
"name": "Mr Bob Chilren",
"Children": []
}, {
"name": "d",
"age": 33, //other props keep
"children": [{
"name": "already lowecased",
"age": 44,
"Children": [{
"name": "now back to upercased",
"age": 99
}]
}] //what if were alrady lowercased?
}]
}, {
"name": "e",
//"Children": [] //what if we have no children
}]
};
console.log(transform(json));

loop and put array within array according to id failed

http://jsfiddle.net/rw0z9e2j/
var sports = [{
"id": 1,
"name": "baseball"
}, {
"id": 2,
"name": "Football"
}];
var playersData = [{
"sport_id": 2,
"id": "nv12",
"name": "James"
}, {
"sport_id": 2,
"id": "nv11",
"name": "Jean"
}];
var arr = [],
tempObj = {};
$.each(sports, function (i, obj) {
var sport_id = obj.id;
$.each(playersData, function (i, obj) {
if (sport_id == obj.sport_id) {
tempObj = {
"sport_id": obj.sport_id,
"id": obj.id,
"name": obj.name
};
arr.push(tempObj);
}
});
obj.players = arr;
});
console.log(sports);
I try to build an array of players and put them within sports group according to sport_id but above logic has failed. It didn't group properly, the player who's in sport_id = 1 should go to sport which its id = 1 but why it didn't?
what's wrong with above loop there?
I suppose this is what you want:
var sports = [{
"id": 1,
"name": "baseball"
}, {
"id": 2,
"name": "Football"
}];
var playersData = [{
"sport_id": 2,
"id": "nv12",
"name": "James"
}, {
"sport_id": 2,
"id": "nv11",
"name": "Jean"
}];
sports.forEach(function (a) {
var arr = [];
playersData.forEach(function (b) {
if (a.id == b.sport_id) {
arr.push(b);
}
});
a.players = arr;
});
document.write('<pre>' + JSON.stringify(sports, 0, 4) + '</pre>');
You're declaring your temp vars outside of your loops, these should be scoped to your loops and thrown away after each operation.
var arr = [],
tempObj = {};
http://jsfiddle.net/samternent/rw0z9e2j/2/
You have to put it after push:
arr.push(tempObj);
obj.players = arr;
Actually you need this:
var sports = [{
"id": 1,
"name": "baseball"
}, {
"id": 2,
"name": "Football"
}];
var playersData = [{
"sport_id": 2,
"id": "nv12",
"name": "James"
}, {
"sport_id": 2,
"id": "nv11",
"name": "Jean"
}];
var arr = [];
$.each(sports, function (i, obj) {
$.each(playersData, function (i, player) {
if (obj.id === player.sport_id) {
var tempObj = {
"sport_id": player.sport_id,
"id": player.id,
"name": player.name
};
arr.push(tempObj);
obj.players = arr;
}
});
});
console.log(sports);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Hope you want to put inside the Sports group, but you are adding inside the player array, please notice, so please call
obj.sports = arr;
Hope it solve your problem .

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

AngularJS - Json to Tree structure

I 've seen lots of answers that turn a Json response to a Ul Tree structure.
The thing is that all these subjects where around a nested response pointing
out the relationship between parent object and child object.
I have a non nested Json response indicating the relation by reference to the objects property.
The following is part of the response:
{"id":"1","parent_id":null,"name":"Item-0"},
{"id":"2","parent_id":"1","name":"Item-1"},
{"id":"3","parent_id":"2","name":"Item-2"},
{"id":"4","parent_id":"2","name":"Item-4"},
{"id":"5","parent_id":"2","name":"Item-5"},
{"id":"6","parent_id":"2","name":"Item-6"},
{"id":"7","parent_id":"2","name":"Item-7"},
{"id":"8","parent_id":"2","name":"Item-8"},
{"id":"9","parent_id":"2","name":"Item-9"},
{"id":"10","parent_id":"1","name":"Item-3"},
{"id":"11","parent_id":"10","name":"Item-10"},
You might already noticed that the each object conects with his father through the parent_id which is conected to his parent's id.
I tried to create a custom directive to read the response and through recursion to build the Tree structure.
Till now I succeeded to only create the first level of the tree.
Demo
app.directive('tree',function(){
var treeStructure = {
restrict: "E",
transclude: true,
root:{
response : "=src",
Parent : "=parent"
},
link: function(scope, element, attrs){
var log = [];
scope.recursion = "";
angular.forEach(scope.response, function(value, key) {
if(value.parent_id == scope.Parent){
this.push(value);
}
}, log);
scope.filteredItems = log;
scope.getLength = function (id){
var test = [];
angular.forEach(scope.response, function(value, key) {
if(value.parent_id == id){
this.push(value);
}
}, test);
if(test.length > 0){
scope.recursion = '<tree src="scope.response" parent="'+id+'"></tree>';
}
return scope.recursion;
};
},
template:
'<ul>'
+'<li ng-repeat="item in filteredItems">'
+'{{item.name}}<br />'
+'{{getLength(item.id)}}'
+'</li>'
+'<ul>'
};
return treeStructure;
});
app.controller('jManajer', function($scope){
$scope.information = {
legend : "Angular controlled JSon response",
};
$scope.response = [
{"id":"1","parent_id":null,"name":"Item-0"},
{"id":"2","parent_id":"1","name":"Item-1"},
{"id":"3","parent_id":"2","name":"Item-3"},
{"id":"4","parent_id":"2","name":"Item-4"},
{"id":"5","parent_id":"2","name":"Item-5"},
{"id":"6","parent_id":"2","name":"Item-6"},
{"id":"7","parent_id":"2","name":"Item-7"},
{"id":"8","parent_id":"2","name":"Item-8"},
{"id":"9","parent_id":"2","name":"Item-9"},
{"id":"10","parent_id":"1","name":"Item-2"},
{"id":"11","parent_id":"10","name":"Item-10"},
];
});
Is there any one who could show me how to convert this kind of array to Tree structure through recursion?
Transform your array first recursive in a nested one
var myApp = angular.module('myApp', []);
myApp.controller('jManajer', function($scope) {
$scope.res = [];
$scope.response = [{
"id": "1",
"parent_id": 0,
"name": "Item-0"
}, {
"id": "2",
"parent_id": "1",
"name": "Item-1"
}, {
"id": "3",
"parent_id": "2",
"name": "Item-3"
}, {
"id": "4",
"parent_id": "2",
"name": "Item-4"
}, {
"id": "5",
"parent_id": "2",
"name": "Item-5"
}, {
"id": "6",
"parent_id": "2",
"name": "Item-6"
}, {
"id": "7",
"parent_id": "2",
"name": "Item-7"
}, {
"id": "8",
"parent_id": "2",
"name": "Item-8"
}, {
"id": "9",
"parent_id": "2",
"name": "Item-9"
}, {
"id": "10",
"parent_id": "1",
"name": "Item-2"
}, {
"id": "11",
"parent_id": "10",
"name": "Item-10"
}, ];
function getNestedChildren(arr, parent) {
var out = []
for (var i in arr) {
if (arr[i].parent_id == parent) {
var children = getNestedChildren(arr, arr[i].id)
if (children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
$scope.res = getNestedChildren($scope.response, "0");
console.log($scope.res);
$scope.nested_array_stingified = JSON.stringify($scope.res);
});
<!DOCTYPE html>
<html>
<head>
<script src="https://code.angularjs.org/1.3.11/angular.js"></script>
</head>
<body ng-app="myApp" ng-controller="jManajer">
{{nested_array_stingified}}
</body>
</html>
After that you can follow the tutorial here for example http://sporto.github.io/blog/2013/06/24/nested-recursive-directives-in-angular/
You need to unflatten your data before passing it to your view.
I have modified your code a bit, also I have transformed your data and replaced the depth and relational fields with integers, it's more easy to compare integers rather than comparing strings and null values.
In this example I have used Undescore.js' powerful functions in order to unflatten the array.
The code can be found as a helper function inside your directive:
unflatten = function (array, parent, tree) {
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : {
id: "0"
};
var children = _.filter(array, function (child) {
return child.parent_id == parent.id;
});
if (!_.isEmpty(children)) {
if (parent.id == "0" || parent.id == null) {
tree = children;
} else {
parent['children'] = children
}
_.each(children, function (child) {
unflatten(array, child)
});
}
console.log(tree)
return tree;
}
If you want a VanillaJs solution check out this piece of code
Also here is a demo simulating an interactive tree with your data
The underscore unflatten function was taken from here .

Categories

Resources