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 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 want to use an array of strings as template how to order other arrays.
var sort = ["this","is","my","custom","order"];
and then i want to sort an array of objects depending on a key (content) by that order:
var myObjects = [
{"id":1,"content":"is"},
{"id":2,"content":"my"},
{"id":3,"content":"this"},
{"id":4,"content":"custom"},
{"id":5,"content":"order"}
];
so that my result is:
sortedObject = [
{"id":3,"content":"this"},
{"id":1,"content":"is"},
{"id":2,"content":"my"},
{"id":4,"content":"custom"},
{"id":5,"content":"order"}
];
how would i do that?
You can do something like this with help of sort() and indexOf()
var sort = ["this", "is", "my", "custom", "order"];
var myObjects = [{
"id": 1,
"content": "is"
}, {
"id": 2,
"content": "my"
}, {
"id": 3,
"content": "this"
}, {
"id": 4,
"content": "custom"
}, {
"id": 5,
"content": "order"
}];
var sortedObj = myObjects.sort(function(a, b) {
return sort.indexOf(a.content) - sort.indexOf(b.content);
});
document.write('<pre>' + JSON.stringify(sortedObj, null, 3) + '</pre>');
you need to use .map
var sort = ["this", "is", "my", "custom", "order"];
var myObjects = [{
"id": 1,
"content": "is"
}, {
"id": 2,
"content": "my"
}, {
"id": 3,
"content": "this"
}, {
"id": 4,
"content": "custom"
}, {
"id": 5,
"content": "order"
}];
var myObjectsSort = sort.map(function(e, i) {
for (var i = 0; i < myObjects.length; ++i) {
if (myObjects[i].content == e)
return myObjects[i];
}
});
document.write('<pre>' + JSON.stringify(myObjectsSort , null, 3) + '</pre>');
Create a new array and place the every object from myObjects considering index of the sort
Try this:
var sort = ["this", "is", "my", "custom", "order"];
var myObjects = [{
"id": 1,
"content": "is"
}, {
"id": 2,
"content": "my"
}, {
"id": 3,
"content": "this"
}, {
"id": 4,
"content": "custom"
}, {
"id": 5,
"content": "order"
}];
var newArr = [];
myObjects.forEach(function(item) {
var index = sort.indexOf(item.content);
newArr[index] = item;
});
console.log(newArr);
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
I suggest to use an object for the storing of the sort order.
var sort = ["this", "is", "my", "custom", "order"],
sortObj = {},
myObjects = [{ "id": 1, "content": "is" }, { "id": 2, "content": "my" }, { "id": 3, "content": "this" }, { "id": 4, "content": "custom" }, { "id": 5, "content": "order" }];
sort.forEach(function (a, i) { sortObj[a] = i; });
myObjects.sort(function (a, b) {
return sortObj[ a.content] - sortObj[ b.content];
});
document.write('<pre>' + JSON.stringify(myObjects, 0, 4) + '</pre>');
Suppose I have a tree of objects like the following, perhaps created using the excellent algorithm found here: https://stackoverflow.com/a/22367819/3123195
{
"children": [{
"id": 1,
"title": "home",
"parent": null,
"children": []
}, {
"id": 2,
"title": "about",
"parent": null,
"children": [{
"id": 3,
"title": "team",
"parent": 2,
"children": []
}, {
"id": 4,
"title": "company",
"parent": 2,
"children": []
}]
}]
}
(Specifically in this example, the array returned by that function is nested as the children array property inside an otherwise empty object.)
How would I convert it back to a flat array?
Hope your are familiar with es6:
let flatten = (children, extractChildren) => Array.prototype.concat.apply(
children,
children.map(x => flatten(extractChildren(x) || [], extractChildren))
);
let extractChildren = x => x.children;
let flat = flatten(extractChildren(treeStructure), extractChildren)
.map(x => delete x.children && x);
UPD:
Sorry, haven't noticed that you need to set parent and level. Please find the new function below:
let flatten = (children, getChildren, level, parent) => Array.prototype.concat.apply(
children.map(x => ({ ...x, level: level || 1, parent: parent || null })),
children.map(x => flatten(getChildren(x) || [], getChildren, (level || 1) + 1, x.id))
);
https://jsbin.com/socono/edit?js,console
This function will do the job, plus it adds a level indicator to each object. Immediate children of treeObj will be level 1, their children will be level 2, etc. The parent properties are updated as well.
function flatten(treeObj, idAttr, parentAttr, childrenAttr, levelAttr) {
if (!idAttr) idAttr = 'id';
if (!parentAttr) parentAttr = 'parent';
if (!childrenAttr) childrenAttr = 'children';
if (!levelAttr) levelAttr = 'level';
function flattenChild(childObj, parentId, level) {
var array = [];
var childCopy = angular.extend({}, childObj);
childCopy[levelAttr] = level;
childCopy[parentAttr] = parentId;
delete childCopy[childrenAttr];
array.push(childCopy);
array = array.concat(processChildren(childObj, level));
return array;
};
function processChildren(obj, level) {
if (!level) level = 0;
var array = [];
obj[childrenAttr].forEach(function(childObj) {
array = array.concat(flattenChild(childObj, obj[idAttr], level+1));
});
return array;
};
var result = processChildren(treeObj);
return result;
};
This solution takes advantage of Angular's angular.extend() function to perform a copy of the child object. Wiring this up with any other library's equivalent method or a native function should be a trivial change.
The output given for the above example would be:
[{
"id": 1,
"title": "home",
"parent": null,
"level": 1
}, {
"id": 2,
"title": "about",
"parent": null,
"level": 1
}, {
"id": 3,
"title": "team",
"parent": 2,
"level": 2
}, {
"id": 4,
"title": "company",
"parent": 2,
"level": 2
}]
It is also worth noting that this function does not guarantee the array will be ordered by id; it will be based on the order in which the individual objects were encountered during the operation.
Fiddle!
Here it goes my contribution:
function flatNestedList(nestedList, childrenName, parentPropertyName, idName, newFlatList, parentId) {
if (newFlatList.length === 0)
newFlatList = [];
$.each(nestedList, function (i, item) {
item[parentPropertyName] = parentId;
newFlatList.push(item);
if (item[childrenName] && item[childrenName].length > 0) {
//each level
flatNestedList(item[childrenName], childrenName, parentPropertyName, idName, newFlatList, item[idName]);
}
});
for (var i in newFlatList)
delete (newFlatList[i][childrenName]);
}
Try following this only assumes each item is having children property
class TreeStructureHelper {
public toArray(nodes: any[], arr: any[]) {
if (!nodes) {
return [];
}
if (!arr) {
arr = [];
}
for (var i = 0; i < nodes.length; i++) {
arr.push(nodes[i]);
this.toArray(nodes[i].children, arr);
}
return arr;
}
}
Usage
let treeNode =
{
children: [{
id: 1,
title: "home",
parent: null,
children: []
}, {
id: 2,
title: "about",
parent: null,
children: [{
id: 3,
title: "team",
parent: 2,
children: []
}, {
id: 4,
title: "company",
parent: 2,
children: []
}]
}]
};
let flattenArray = _treeStructureHelper.toArray([treeNode], []);
This is data:
const data = {
id: '1',
children: [
{
id: '2',
children: [
{
id: '4',
children: [
{
id: '5'
},
{
id: '6'
}
]
},
{
id: '7'
}
]
},
{
id: '3',
children: [
{
id: '8'
},
{
id: '9'
}
]
}
]
}
In React.JS just declare an array field in state and push items to that array.
const getAllItemsPerChildren = item => {
array.push(item);
if (item.children) {
return item.children.map(i => getAllItemsPerChildren(i));
}
}
After function call your array in state will hold all items as below:
One more 😄😁
function flatten(root, parent=null, depth=0, key='id', flat=[], pick=() => {}) {
flat.push({
parent,
[key]: root[key],
depth: depth++,
...pick(root, parent, depth, key, flat)
});
if(Array.isArray(root.children)) {
root.children.forEach(child => flatten(child, root[key], depth, key, flat, pick));
}
}
let sample = {
"id": 0,
"children": [{
"id": 1,
"title": "home",
"parent": null,
"children": []
}, {
"id": 2,
"title": "about",
"parent": null,
"children": [{
"id": 3,
"title": "team",
"parent": 2,
"children": []
}, {
"id": 4,
"title": "company",
"parent": 2,
"children": []
}]
}]
};
let flat = [];
flatten(sample, null, 0, 'id', flat, root => ({ title: root.title }));
let expected = [
{
"id": 0,
"parent": null,
"depth": 0
},
{
"id": 1,
"parent": 0,
"depth": 1,
"title": "home"
},
{
"id": 2,
"parent": 0,
"depth": 1,
"title": "about"
},
{
"id": 3,
"parent": 2,
"depth": 2,
"title": "team"
},
{
"id": 4,
"parent": 2,
"depth": 2,
"title": "company"
}
];
How can I populate Kendo UI grid with nested JSON.
I mean my JSON is like
var myJson:
[{"oneType":[
{"id":1,"name":"John Doe"},
{"id":2,"name":"Don Joeh"}
]},
{"othertype":"working"},
{"otherstuff":"xyz"}]
}];
and I want Kendo UI Grid with columns as Id, Name, OtherType and OtherStuff.
Thanks in advance.!
For complex JSON structures, you might use schema.parse
var grid = $("#grid").kendoGrid({
dataSource : {
data : [
{
"oneType": [
{"id": 1, "name": "John Doe"},
{"id": 2, "name": "Don Joeh"}
]
},
{"othertype": "working"},
{"otherstuff": "xyz"}
],
pageSize: 10,
schema : {
parse : function(d) {
for (var i = 0; i < d.length; i++) {
if (d[i].oneType) {
return d[i].oneType;
}
}
return [];
}
}
}
}).data("kendoGrid");
If you slightly change your JSON to:
{
"oneType" : [
{"id": 1, "name": "John Doe"},
{"id": 2, "name": "Don Joeh"}
],
"othertype" : "working",
"otherstuff": "xyz"
}
then you can use:
var grid = $("#grid").kendoGrid({
dataSource: {
data : {
"oneType" : [
{"id": 1, "name": "John Doe"},
{"id": 2, "name": "Don Joeh"}
],
"othertype" : "working",
"otherstuff": "xyz"
},
pageSize: 10,
schema : {
data: "oneType"
}
}
}).data("kendoGrid");
I just wanted to submit another answer based on OnaBai's.
http://jsfiddle.net/L6LwW/17/
The HTML:
<script id="message-template" type="text/x-kendo-template">
#for (var i = 0; i
< ddl.length; i++) {# <li><span>#=ddl[i].value#</li>
#}#
</script>
<div id="grid"></div>
The JS:
var grid = $("#grid").kendoGrid({
dataSource: {
data: [
[{
"id": 1,
"name": "John Doe",
"ddl": [{
"key": 1,
"value": "hello"
}, {
"key": 1,
"value": "hello"
}]
}, {
"id": 2,
"name": "Don Joeh",
"ddl": [{
"key": 1,
"value": "hello"
}, {
"key": 1,
"value": "hello"
}]
}]
],
pageSize: 10,
schema: {
parse: function(d) {
for (var i = 0; i < d.length; i++) {
if (d[i]) {
return d[i];
}
}
return [];
}
}
},
columns: [{
field: "id",
title: "ID"
}, {
field: "name",
title: "Name"
}, {
field: "ddl",
title: "DDL",
width: "180px",
template: kendo.template($("#message-template").html())
} //template: "#=ddl.value#" }
]
}).data("kendoGrid");