I am a little stuck on this. I want to create a tree structure from a flat array. Say I have this input:
var input = [
["a","b","c"],
["a", "b","d"],
["e","f","g"],
];
I want to create a tree structure looking like the following:
// output:
[
{
id: "a",
children: [
{id: "b", children: [
{id: "c", children: []},
{id: "d", children: []}
]},
]
},
{
id: "e",
children: [
{
id: "f",
children: [{ id: "g", children: []}]
},
]
}
]
One way I was thinking of doing this was having a map of all of the parent and iterate through the input array to set the parent to child mappings. But I come to problems when trying to actually construct the tree object from that map and avoiding duplicates. Any pointers are appreciated, thanks!
I found the solution to a problem that is similar to your question.
_makeTree
If you have data that looks like this:
_makeTree({ q:
[
{"id": 123, "parentid": 0, "name": "Mammals"},
{"id": 456, "parentid": 123, "name": "Dogs"},
{"id": 214, "parentid": 456, "name": "Labradors"},
{"id": 810, "parentid": 456, "name": "Pugs"},
{"id": 919, "parentid": 456, "name": "Terriers"}
]
});
Parameters:
q (Array): A query result (see example below)
id (String): The name of the id column (Default: "id")
parentid (String): The name of the ParentItemID column (Default: "parentid")
children (String): The name of the "children" array to be created in rows that have children (Default: "children")
Then result should be something like the following structure:
[
{
"id": 123,
"parentid": 0,
"name": "Mammals",
"children": [
{
"id": 456,
"parentid": 123,
"name": "Dogs",
"children": [
{
"id": 214,
"parentid": 456,
"name": "Labradors"
},
{
"id": 810,
"parentid": 456,
"name": "Pugs"
},
{
"id": 919,
"parentid": 456,
"name": "Terriers"
}
]
}
]
}
]
Now, _makeTree codes:
var _makeTree = function(options) {
var children, e, id, o, pid, temp, _i, _len, _ref;
id = options.id || "id";
pid = options.parentid || "parentid";
children = options.children || "children";
temp = {};
o = [];
_ref = options.q;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
e = _ref[_i];
temp[e[id]] = e;
if (temp[e[pid]] != null) {
if (temp[e[pid]][children] == null) {
temp[e[pid]][children] = [];
}
temp[e[pid]][children].push(e);
} else {
o.push(e);
}
}
return o;
};
References:
I need to create a custom tree data-structure using JavaScript
Creating trees from SQL queries in Javascript
_makeTree library
Related
Today I was working on a problem, which states as follows:
Problem:
INPUT: [{..}, {..}, ..] Array of objects;
Each object is has {"id": required, "children": []}
The objects has parent-child relation based on "id" and "children" props
OUTPUT: [{..}, {..}, ..] Array in a tree (hierarchy) order :multi-level.
Input:
[{
"id": 1,
"name": "Earth",
"children": [2, 3]
}, {
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}, {
"id": 4,
"name": "Germany",
"children": [5]
}, {
"id": 5,
"name": "Hamburg",
"children": []
}]
OutPut
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [{
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
}]
}]
My approach
I decided to solve this by iterating through each element in the array and recursively find and append objects to children of each element.
So just to start with, I decided to have only First level children appended their respective parents. And my code is following.
var posts = [{"id":1,"name":"Earth","children":[2,3]},{"id":2,"name":"Asia","children":[]},{"id":3,"name":"Europe","children":[4]},{"id":4,"name":"Germany","children":[5]},{"id":5,"name":"Hamburg","children":[]}]
function getElementById (id, posts) {
for(var i =0; i< posts.length; i++){
if(posts[i].id === id){
var found = posts[i];
///// FUN here -> //// posts.splice(i, 1);
return found;
}
}
}
function refactorChildren(element, posts) {
if(!element.children || element.children.length === 0) {
return element;
}
var children = [];
for(var i = 0; i < element.children.length; i++){
var childElement = getElementById(element.children[i], posts);
children.push(childElement);
}
element.children = children;
return element;
}
function iterate(posts) {
var newPosts = [];
var des = [...posts]
for(var i = 0; i < des.length; i++){
var childedElement = refactorChildren(des[i], des);
newPosts.push(childedElement);
}
return newPosts;
}
var filtered = iterate(posts);
console.log(JSON.stringify(filtered))
Surprisingly above code Solves the ACTUAL PROBLEM (except a lil bit of more work)
My Expected Result should be the following: Array of objects with only First level children
[{
"id": 1,
"name": "Earth",
"children": [{
"id": 2,
"name": "Asia",
"children": []
}, {
"id": 3,
"name": "Europe",
"children": [4]
}]
}, {
"id": 4,
"name": "Germany",
"children": [{
"id": 5,
"name": "Hamburg",
"children": []
}]
}]
And I do get the above result if I uncomment the ///// FUN here -> //// line. Which is erasing the iterating object on the go.
So my problem is
I want to know - HOW DID? All the objects got appended correctly to their respective Parent objects by that code? My next step was to add a recursion call to the function refactorChildren(with-childElement).
AND
How did, just by adding posts.splice(i, 1); got me MY expected result from the code?
Please help me understand, I just cant go ahead without knowing "HOW".
Thanks
While traversing the objects, you recursively call a function on all its chilfren and remove the objects from the array:
[
{ id: 1, children: [2], }, // < iterator
{ id: 2, children: [] }, // < gets spliced out recursively
]
If a child is in the array before its parent however, this won't work as you copy the child into another array before the parent gets visited.
Maybe you are interested in a different approach with only a single loop for getting the parent elements and their children.
This works for unsorted data, too.
var data = [{ id: 1, name: "Earth", children: [2, 3] }, { id: 2, name: "Asia", children: [] }, { id: 3, name: "Europe", children: [4] }, { id: 4, name: "Germany", children: [5] }, { id: 5, name: "Hamburg", children: [] }],
tree = function (array) {
var r = {},
children = new Set,
result = [];
array.forEach(o => {
Object.assign(
r[o.id] = r[o.id] || {},
o,
{ children: o.children.map(id => (children.add(id), r[id] = r[id] || {})) }
);
});
return Object.values(r).filter(({ id }) => !children.has(id));
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I got a json object like this
{
"id": 1,
"name": "A",
"nodes": [
{
"id": 2,
"name": "B",
"nodes": [
{
"id": 3,
"name": "C",
"nodes": []
}
]
}
]
}
If I have as input the id of the object, lets take id: 3, how would I scan the whole three find the object with specific id and then scan upwards to the last parent.
So after the scan is done I know that C has parent B and B has parent A, so I can then print that like A-B-C
all based on me knowing the id of object I want to find parents of.
The above object can be of any lenght and can have many nodes and levels. So anyone has any idea how to traverse up the levels to top level if starting at specific level?
edit:
when I try to parse this
let data = [
{
"id": 1,
"name": "name",
"testing": "something",
"nodes": [
{
"id": 11,
"name": "name",
"testing": "something",
"nodes": []
}
]
},
{
"id": 2,
"name": "name",
"testing": "something",
"nodes": []
}
]
to json object by doing JSON.parse(data) I get an error
SyntaxError: Unexpected token o in JSON at position 1
at JSON.parse (<anonymous>)
Also tried this
let jsonObject = JSON.stringify($scope.data);
jsonObject = JSON.parse(jsonObject);
createTree(jsonObject, null, nodeData.id)
and get different error:
TypeError: obj.nodes is not iterable
Do a basic DFS scan, add parent property along the way, and climb up when node found.
let jsonParsed = JSON.parse(`
{
"id": 1,
"name": "A",
"nodes": [
{
"id": 2,
"name": "B",
"nodes": [
{
"id": 3,
"name": "C",
"nodes": []
}
]
}
]
}
`)
let arr = []
function climbTree(obj) {
arr.unshift(obj.name)
if (obj.parent) {
climbTree(obj.parent)
}
}
function createTree(obj, parent = null, targetId = null) {
obj.parent = parent
if (targetId === obj.id) {
return climbTree(obj)
}
for (let node of obj.nodes) {
createTree(node, obj, targetId)
}
}
createTree(jsonParsed, null, 3)
console.log(arr.join('-'))
I have an nested json object in which I need to remove empty values and create new json which should contain only data objects.
json file:
myData = [{
"id": 1,
"values": [{
"value": ""
}]
}, {
"id": 2,
"values": [{
"value": 213
}]
}, {
"id": 3,
"values": [{
"value": ""
}, {
"value": ""
}, {
"value": "abc"
}]
},{
"id": 4,
"values": [{
"value": ""
}]
},{
"id": 33,
"values": [{
"value": "d"
}]
}];
Output should be:
myNewData = [{
"id": 2,
"values": [{
"value": 213
}]
}, {
"id": 3,
"values": [{
"value": "abc"
}]
},{
"id": 33,
"values": [{
"value": "d"
}]
}];
So far I have created this:
angular.module('myapp',[])
.controller('test',function($scope){
$scope.myData = [{
"id": 1,
"values": [{
"value": ""
}]
}, {
"id": 2,
"values": [{
"value": 213
}]
}, {
"id": 3,
"values": [{
"value": ""
}, {
"value": ""
}, {
"value": "abc"
}]
},{
"id": 4,
"values": [{
"value": ""
}]
},{
"id": 33,
"values": [{
"value": "d"
}]
}];
})
.filter('filterData',function(){
return function(data) {
var dataToBePushed = [];
data.forEach(function(resultData){
if(resultData.values && resultData.values != "")
dataToBePushed.push(resultData);
});
return dataToBePushed;
}
});
Html:
<div ng-app="myapp">
<div ng-controller="test">
<div ng-repeat="data in myData | filterData">
Id:{{ data.id }}
</br>
Values: {{ data.values }}
</div>
</div>
</div>
I am not able to access and remove value inside values object. Right now i am simply showing the data using ng-repeat but i need to create a new json file for that.
You work with the array in your AngularJS Controller doing Array.prototype.map() and Array.prototype.filter(). Map all objects doing a filter to exclude the items with empty values item.values.value, and than a filter to get the array elements that have values with value:
var myData = [{"id": 1,"values": [{ "value": ""}]}, {"id": 2,"values": [{"value": 213}]}, {"id": 3,"values": [{"value": ""}, {"value": ""}, {"value": "abc"}]}, {"id": 4,"values": [{"value": ""}]}, {"id": 33,"values": [{"value": "d"}]}],
myDataFiltered = myData
.map(function (item) {
item.values = item.values.filter(function (itemValue) {
return itemValue.value;
});
return item;
})
.filter(function (item) {
return item.values.length;
});
console.log(myDataFiltered);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6:
myDataFiltered = myData
.map(item => {
item.values = item.values.filter(itemValue => itemValue.value);
return item;
})
.filter(item => item.values.length);
Here you go with a multiple for-loop.
myData = [{
"id": 1,
"values": [{
"value": ""
}]
}, {
"id": 2,
"values": [{
"value": 213
}]
}, {
"id": 3,
"values": [{
"value": ""
}, {
"value": ""
}, {
"value": "abc"
}]
},{
"id": 4,
"values": [{
"value": ""
}]
},{
"id": 33,
"values": [{
"value": "d"
}]
}];
function clone(obj){ return JSON.parse(JSON.stringify(obj));}
var result = [];
for(var i = 0; i < myData.length; i++){
var current = clone(myData[i]);
for(var j = 0; j < current.values.length; j++){
if(current.values[j].value == null || current.values[j].value == ""){
current.values.splice(j, 1);
j--;
}
}
if(current.values.length > 0) result.push(current);
}
console.log(myData);
console.log(result);
If you want to delete them completely, you can iterate over the array like this;
angular.forEach($scope.myData, function(data){
for(var i=0; i < data.values.length; i++){
if(data.values[i] !== ""){
break;
}
delete data;
}
});
The if statement checks all values in the array, and breaks if it's not equal to "", otherwise if all values are = "" it deletes the object.
Hope it helps!
Here's a recursive function to do the job.
This will only work if myData is an array and the value inside it or its children is a collection of object.
var myData = [{"id": 1, "values": [{"value": ""}] }, {"id": 2, "values": [{"value": 213 }] }, {"id": 3, "values": [{"value": ""}, {"value": ""}, {"value": "abc"}] },{"id": 4, "values": [{"value": ""}] },{"id": 6, "values": ""},{"id": 33, "values": [{"value": "d"}] }];
function removeEmptyValues (arr) {
var res = false;
/* Iterate the array */
for (var i = 0; i < arr.length; i++) {
/* Get the object reference in the array */
var obj = arr[i];
/* Iterate the object based on its key */
for (var key in obj) {
/* Ensure the object has the key or in the prototype chain */
if (obj.hasOwnProperty(key)) {
/* So, the object has the key. And we want to check if the object property has a value or not */
if (!obj[key]) {
/*
If it has no value (null, undefined, or empty string) in the property, then remove the whole object,
And reduce `i` by 1, to do the re-checking
*/
arr.splice(i--, 1);
/* Amd set whether the removal occurance by setting it to res (result), which we will use for the next recursive function */
res = true;
/* And get out from the loop */
break;
}
/* So, the object has a value. Let's check whether it's an array or not */
if (Array.isArray(obj[key])) {
/* Kay.. it's an array. Let's see if it has anything in it */
if (!obj[key].length) {
/* There's nothing in it !! Remove the whole object again */
arr.splice(i--, 1);
/* Amd set whether the removal occurance by setting it to res (result), which we will use for the next recursive function */
res = true;
/* Yes.. gets out of the loop */
break;
}
/*
Now this is where `res` is being used.
If there's something removed, we want to re-do the checking of the whole object
*/
if ( removeEmptyValues(obj[key]) ) {
/* Something has been removed, re-do the checking */
i--;
}
}
}
}
}
return res;
}
removeEmptyValues (myData);
Try this:
var myData = [{"id": 1,"values": [{ "value": ""}]}, {"id": 2,"values": [{"value": 213}]}, {"id": 3,"values": [{"value": ""}, {"value": ""}, {"value": "abc"}]}, {"id": 4,"values": [{"value": ""}]}, {"id": 33,"values": [{"value": "d"}]}]
let result=[],p=[];
myData.filter(el => {
p=[];
el.values.filter(k => {k.value != '' ? p.push({value : k.value}) : null});
if(p.length) result.push({id : el.id, values : p})
})
console.log('result', result);
You are going to right way but need some more operation like this :
angular.module('myapp',[])
.controller('test',function($scope){
$scope.myData = [{
"id": 1,
"values": [{
"value": ""
}]
}, {
"id": 2,
"values": [{
"value": 213
}]
}, {
"id": 3,
"values": [{
"value": ""
}, {
"value": ""
}, {
"value": "abc"
}]
},{
"id": 4,
"values": [{
"value": ""
}]
},{
"id": 33,
"values": [{
"value": "d"
}]
}];
})
.filter('filterData',function($filter){
return function(data) {
var dataToBePushed = [];
data.forEach(function(resultData){
var newValues=resultData;
var hasData=$filter('filter')(resultData.values,{value:'!'},true);
if(resultData.values && resultData.values.length>0 && hasData.length>0){
newValues.values=hasData;
dataToBePushed.push(newValues);
}
});
debugger;
return dataToBePushed;
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="test">
<div ng-repeat="data in myData | filterData:''">
Id:{{ data.id }}
</br>
Values: {{ data.values }}
</div>
</div>
</div>
I have the following events array. For every event there is a hash as {organisation name: [{participant 1}, {participant 2}, {...}]}
"events": [
{
"Org A": [
{
"event_id": 1,
"id": 432,
"name": "John Doe",
"role": null
},
{
"event_id": 1,
"id": 312,
"name": "Jane Mow",
"role": [
"speaker"
]
}
],
}
],
I would like to filter this events array to only contain participants whose role contains speaker.
Also, when there are no speakers in the participant array, the respective organisation entry needs to be removed from the Hash (object).
To filter the array of objects, I tried using this:
_.each(events, function(event){
_.filter(event, function(p) {
_.filter(p, function(d){
return _.some(d.role, function(r){
return r == "speaker"})
})
})
})
This however doesn't work.
Try this
var data = {
"events": [{
"Org A": [{
"event_id": 1,
"id": 432,
"name": "John Doe",
"role": null
}, {
"event_id": 1,
"id": 312,
"name": "Jane Mow",
"role": [
"speaker"
]
}],
"Org B": [],
"Org C": []
}]
};
var SPEAKER = 'speaker';
var result = _.map(data.events, function (events) {
return _.chain(events)
.mapObject(function (value, key) {
return _.filter(value, function (event) {
return _.isArray(event.role) && _.indexOf(event.role, SPEAKER) >= 0;
});
})
.pick(function (value) {
return value && value.length;
})
.value();
})
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
I have a list of records and i convert it to json:
[ { "id": 1, "name": "A", "parentID": 0, "hasItems": "true" },
{ "id": 2, "name": "B", "parentID": 1, "hasItems": "false" },
{ "id": 3, "name": "C", "parentID": 1, "hasItems": "false" },
{ "id": 4, "name": "D", "parentID": 0, "hasItems": "false" }
]
Now i want create a KendoTreeView from above json data:
<div id="treeview55"></div>
<script>
dtSrc = new kendo.data.HierarchicalDataSource({
transport: {
read: {
url: "http://localhost:1132/Discontent/GetTreeNodes",
dataType: "json"
}
},
,
schema:{
model: {
id: 'id',
parentId: 'parentID',
name: 'name'
}
}
});
$("#treeview55").kendoTreeView({
dataSource: dtSrc,
dataTextField: "name",
dataValueField: 'id',
});
Result:
A
B
C
D
My Expected Result:
> A
B
C
D
My question:
Is there any way to create a KendoTreeView with above expected result (cascade children and parents) by above json data???
Refer below part of code will hep you to solve your problem
<div id="tree"></div>
<script>
var flatData = [ { "id": 1, "text": "A", "parent": 0, "hasItems": "true" },
{ "id": 2, "text": "B", "parent": 1, "hasItems": "false" },
{ "id": 3, "text": "C", "parent": 1, "hasItems": "false" },
{ "id": 4, "text": "D", "parent": 0, "hasItems": "false" }
];
function processTable(data, idField, foreignKey, rootLevel) {
var hash = {};
for (var i = 0; i < data.length; i++) {
var item = data[i];
var id = item[idField];
var parentId = item[foreignKey];
hash[id] = hash[id] || [];
hash[parentId] = hash[parentId] || [];
item.items = hash[id];
hash[parentId].push(item);
}
return hash[rootLevel];
}
// the tree for visualizing data
$("#tree").kendoTreeView({
dataSource: processTable(flatData, "id", "parent", 0),
loadOnDemand: false
});
</script>
i just prepare a sample based on your sample data, i hopes it helps to you.
Find answer from this jsbin
At this moment, no. The Kendo UI TreeView works with hierarchical data, and the above is a flattened hierarchy. You need to process the data it so that it becomes hierarchical, like shown in this excellent answer by Jon Skeet.