parent and child json tree for d3.js in powerbi - javascript

I am building customer visual right now using power bi D3.js Visual.
D3 in power bi gives me the following JSON as input :
var arr = [
{ "source" : "BBB", "target" : "AAA"},
{ "source" : "CCC ", "target" : "BBB"},
{ "source" : "DDDD", "target" : "AAA"},
{ "source" : "SSSS", "target" : "CCC"},
{ "source" : "EEEE", "target" : "BBB"},
{ "source" : "FFFF", "target" : "DDDD"},
But what I need is:
var arr =
{
"source": "AAA",
"children": [
{
"source": "BBB",
"children": [
{ "source": "CCC",
"children": [
{ "source": "SSSS"] },
{ "source": "EEEE" }
]
},
{ "source": "DDDD",
"children": [
{ "source": "FFFF" },
] },
]
}
Can someone help one to get this format in javascript?
I tired with following script that I find on other posts, I made some small changes already,just cant get to work.
function unflatten(arr) {
var tree = [],
mappedArr = {},
arrElem,
mappedElem;
// First map the nodes of the array to an object -> create a hash table.
for(var i = 0, len = arr.length; i < len; i++) {
arrElem = arr[i];
mappedArr[arrElem.child] = arrElem;
mappedArr[arrElem.child]['children'] = [];
}
for (var id in mappedArr) {
if (mappedArr.hasOwnProperty(id)) {
console.log(mappedArr.hasOwnProperty(id))
mappedElem = mappedArr[id];
//console.log(mappedElem)
// If the element is not at the root level, add it to its parent array of children.
if (mappedElem.parent!="0") {
mappedArr[mappedElem['parent']]['children'].push(mappedElem);
}
// If the element is at the root level, add it to first level elements array.
else {
tree.push(mappedElem);
}
}
}
return tree;
}

There are many ways to solve this, here is just one:
const arr = [
{ source: "BBB", target: "AAA" },
{ source: "CCC", target: "BBB" },
{ source: "DDDD", target: "AAA" },
{ source: "SSSS", target: "CCC" },
{ source: "EEEE", target: "BBB" },
{ source: "FFFF", target: "DDDD" }
];
const trunk = arr.find(x => !arr.some(y => y.source === x.target)).target;
const branches = arr.reduce((acc, x) => {
acc[x.target] = acc[x.target] || [];
acc[x.target].push(x.source);
return acc;
}, {});
const tree = buildTree(trunk, branches);
function buildTree(source, branches) {
const tree = { source };
if (branches[source]) {
tree.children = branches[source].map(x => buildTree(x, branches));
}
return tree;
}
console.log("Trunk: ", trunk);
console.log("Branches: ", branches);
console.log("Output: ", JSON.stringify(tree, null, 2));
The idea is that you:
Find the trunk of the tree
Find all branches that have children
Build the tree, starting from the trunk and fill every branch with its children recursively
Further clarifications:
arr.find(x => !arr.some(y => y.source === x.target)) looks for a branch that has no father
The some() method tests whether at least one element in the array passes the test. In this case, since it has a !, it will test that no element in the array has a father.
The find() method returns the value of the first branch that has no father.
In the case you consider having multiple trunks you can substitute the find() method with a filter() method, which will return an array of all the trunks. Then you would have to modify the algorithm like this:
const arr = [
{ source: "BBB", target: "AAA" },
{ source: "CCC", target: "BBB" },
{ source: "DDDD", target: "AAA" },
{ source: "SSSS", target: "CCC" },
{ source: "EEEE", target: "BBB" },
{ source: "FFFF", target: "DDDD" },
{ source: "XXXX", target: "YYYY" }
];
const trunks = arr.filter(x => !arr.some(y => y.source === x.target));
const branches = arr.reduce((acc, x) => {
acc[x.target] = acc[x.target] || [];
acc[x.target].push(x.source);
return acc;
}, {});
const trees = trunks.map(x => buildTree(x.target, branches));
function buildTree(source, branches) {
const tree = { source };
if (branches[source]) {
tree.children = branches[source].map(x => buildTree(x, branches));
}
return tree;
}
console.log("Trunk: ", trunks);
console.log("Branches: ", branches);
console.log("Output: ", JSON.stringify(trees, null, 2));

Related

Javascript customize json data format [duplicate]

I have a complex json file that I have to handle with javascript to make it hierarchical, in order to later build a tree.
Every entry of the json has :
id : a unique id,
parentId : the id of the parent node (which is 0 if the node is a root of the tree)
level : the level of depth in the tree
The json data is already "ordered". I mean that an entry will have above itself a parent node or brother node, and under itself a child node or a brother node.
Input :
{
"People": [
{
"id": "12",
"parentId": "0",
"text": "Man",
"level": "1",
"children": null
},
{
"id": "6",
"parentId": "12",
"text": "Boy",
"level": "2",
"children": null
},
{
"id": "7",
"parentId": "12",
"text": "Other",
"level": "2",
"children": null
},
{
"id": "9",
"parentId": "0",
"text": "Woman",
"level": "1",
"children": null
},
{
"id": "11",
"parentId": "9",
"text": "Girl",
"level": "2",
"children": null
}
],
"Animals": [
{
"id": "5",
"parentId": "0",
"text": "Dog",
"level": "1",
"children": null
},
{
"id": "8",
"parentId": "5",
"text": "Puppy",
"level": "2",
"children": null
},
{
"id": "10",
"parentId": "13",
"text": "Cat",
"level": "1",
"children": null
},
{
"id": "14",
"parentId": "13",
"text": "Kitten",
"level": "2",
"children": null
},
]
}
Expected output :
{
"People": [
{
"id": "12",
"parentId": "0",
"text": "Man",
"level": "1",
"children": [
{
"id": "6",
"parentId": "12",
"text": "Boy",
"level": "2",
"children": null
},
{
"id": "7",
"parentId": "12",
"text": "Other",
"level": "2",
"children": null
}
]
},
{
"id": "9",
"parentId": "0",
"text": "Woman",
"level": "1",
"children":
{
"id": "11",
"parentId": "9",
"text": "Girl",
"level": "2",
"children": null
}
}
],
"Animals": [
{
"id": "5",
"parentId": "0",
"text": "Dog",
"level": "1",
"children":
{
"id": "8",
"parentId": "5",
"text": "Puppy",
"level": "2",
"children": null
}
},
{
"id": "10",
"parentId": "13",
"text": "Cat",
"level": "1",
"children":
{
"id": "14",
"parentId": "13",
"text": "Kitten",
"level": "2",
"children": null
}
}
]
}
There is an efficient solution if you use a map-lookup. If the parents always come before their children you can merge the two for-loops. It supports multiple roots. It gives an error on dangling branches, but can be modified to ignore them. It doesn't require a 3rd-party library. It's, as far as I can tell, the fastest solution.
function list_to_tree(list) {
var map = {}, node, roots = [], i;
for (i = 0; i < list.length; i += 1) {
map[list[i].id] = i; // initialize the map
list[i].children = []; // initialize the children
}
for (i = 0; i < list.length; i += 1) {
node = list[i];
if (node.parentId !== "0") {
// if you have dangling branches check that map[node.parentId] exists
list[map[node.parentId]].children.push(node);
} else {
roots.push(node);
}
}
return roots;
}
var entries = [{
"id": "12",
"parentId": "0",
"text": "Man",
"level": "1",
"children": null
},
{
"id": "6",
"parentId": "12",
"text": "Boy",
"level": "2",
"children": null
},
{
"id": "7",
"parentId": "12",
"text": "Other",
"level": "2",
"children": null
},
{
"id": "9",
"parentId": "0",
"text": "Woman",
"level": "1",
"children": null
},
{
"id": "11",
"parentId": "9",
"text": "Girl",
"level": "2",
"children": null
}
];
console.log(list_to_tree(entries));
If you're into complexity theory this solution is Θ(n log(n)). The recursive-filter solution is Θ(n^2) which can be a problem for large data sets.
( BONUS1 : NODES MAY or MAY NOT BE ORDERED )
( BONUS2 : NO 3RD PARTY LIBRARY NEEDED, PLAIN JS )
( BONUS3 : User "Elias Rabl" says this is the most performant solution, see his answer below )
Here it is:
const createDataTree = dataset => {
const hashTable = Object.create(null);
dataset.forEach(aData => hashTable[aData.ID] = {...aData, childNodes: []});
const dataTree = [];
dataset.forEach(aData => {
if(aData.parentID) hashTable[aData.parentID].childNodes.push(hashTable[aData.ID])
else dataTree.push(hashTable[aData.ID])
});
return dataTree;
};
Here is a test, it might help you to understand how the solution works :
it('creates a correct shape of dataTree', () => {
const dataSet = [{
"ID": 1,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady"
}, {
"ID": 2,
"parentID": 1,
"Phone": "(979) 486-1932",
"City": "Chełm",
"Name": "Scarlet"
}];
const expectedDataTree = [{
"ID": 1,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady",
childNodes: [{
"ID": 2,
"parentID": 1,
"Phone": "(979) 486-1932",
"City": "Chełm",
"Name": "Scarlet",
childNodes : []
}]
}];
expect(createDataTree(dataSet)).toEqual(expectedDataTree);
});
As mentioned by #Sander, #Halcyon`s answer assumes a pre-sorted array, the following does not. (It does however assume you have loaded underscore.js - though it could be written in vanilla javascript):
Code
// Example usage
var arr = [
{'id':1 ,'parentid' : 0},
{'id':2 ,'parentid' : 1},
{'id':3 ,'parentid' : 1},
{'id':4 ,'parentid' : 2},
{'id':5 ,'parentid' : 0},
{'id':6 ,'parentid' : 0},
{'id':7 ,'parentid' : 4}
];
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.parentid == parent.id; });
if( !_.isEmpty( children ) ){
if( parent.id == 0 ){
tree = children;
}else{
parent['children'] = children
}
_.each( children, function( child ){ unflatten( array, child ) } );
}
return tree;
}
tree = unflatten( arr );
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " "))
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
Requirements
It assumes the properties 'id' and 'parentid' indicate ID and parent ID respectively. There must be elements with parent ID 0, otherwise you get an empty array back. Orphaned elements and their descendants are 'lost'
http://jsfiddle.net/LkkwH/1/
Use this ES6 approach. Works like charm
// Data Set
// One top level comment
const comments = [{
id: 1,
parent_id: null
}, {
id: 2,
parent_id: 1
}, {
id: 3,
parent_id: 1
}, {
id: 4,
parent_id: 2
}, {
id: 5,
parent_id: 4
}];
const nest = (items, id = null, link = 'parent_id') =>
items
.filter(item => item[link] === id)
.map(item => ({ ...item, children: nest(items, item.id) }));
console.log(
nest(comments)
)
Had the same problem, but I could not be certain that the data was sorted or not. I could not use a 3rd party library so this is just vanilla Js; Input data can be taken from #Stephen's example;
var arr = [
{'id':1 ,'parentid' : 0},
{'id':4 ,'parentid' : 2},
{'id':3 ,'parentid' : 1},
{'id':5 ,'parentid' : 0},
{'id':6 ,'parentid' : 0},
{'id':2 ,'parentid' : 1},
{'id':7 ,'parentid' : 4},
{'id':8 ,'parentid' : 1}
];
function unflatten(arr) {
var tree = [],
mappedArr = {},
arrElem,
mappedElem;
// First map the nodes of the array to an object -> create a hash table.
for(var i = 0, len = arr.length; i < len; i++) {
arrElem = arr[i];
mappedArr[arrElem.id] = arrElem;
mappedArr[arrElem.id]['children'] = [];
}
for (var id in mappedArr) {
if (mappedArr.hasOwnProperty(id)) {
mappedElem = mappedArr[id];
// If the element is not at the root level, add it to its parent array of children.
if (mappedElem.parentid) {
mappedArr[mappedElem['parentid']]['children'].push(mappedElem);
}
// If the element is at the root level, add it to first level elements array.
else {
tree.push(mappedElem);
}
}
}
return tree;
}
var tree = unflatten(arr);
document.body.innerHTML = "<pre>" + (JSON.stringify(tree, null, " "))
JS Fiddle
Flat Array to Tree
a more simple function list-to-tree-lite
npm install list-to-tree-lite
listToTree(list)
source:
function listToTree(data, options) {
options = options || {};
var ID_KEY = options.idKey || 'id';
var PARENT_KEY = options.parentKey || 'parent';
var CHILDREN_KEY = options.childrenKey || 'children';
var tree = [],
childrenOf = {};
var item, id, parentId;
for (var i = 0, length = data.length; i < length; i++) {
item = data[i];
id = item[ID_KEY];
parentId = item[PARENT_KEY] || 0;
// every item may have children
childrenOf[id] = childrenOf[id] || [];
// init its children
item[CHILDREN_KEY] = childrenOf[id];
if (parentId != 0) {
// init its parent's children object
childrenOf[parentId] = childrenOf[parentId] || [];
// push it into its parent's children object
childrenOf[parentId].push(item);
} else {
tree.push(item);
}
};
return tree;
}
jsfiddle
You can handle this question with just two line coding:
_(flatArray).forEach(f=>
{f.nodes=_(flatArray).filter(g=>g.parentId==f.id).value();});
var resultArray=_(flatArray).filter(f=>f.parentId==null).value();
Test Online (see the browser console for created tree)
Requirements:
1- Install lodash 4 (a Javascript library for manipulating objects and collections with performant methods => like the Linq in c#) Lodash
2- A flatArray like below:
var flatArray=
[{
id:1,parentId:null,text:"parent1",nodes:[]
}
,{
id:2,parentId:null,text:"parent2",nodes:[]
}
,
{
id:3,parentId:1,text:"childId3Parent1",nodes:[]
}
,
{
id:4,parentId:1,text:"childId4Parent1",nodes:[]
}
,
{
id:5,parentId:2,text:"childId5Parent2",nodes:[]
}
,
{
id:6,parentId:2,text:"childId6Parent2",nodes:[]
}
,
{
id:7,parentId:3,text:"childId7Parent3",nodes:[]
}
,
{
id:8,parentId:5,text:"childId8Parent5",nodes:[]
}];
Thank Mr. Bakhshabadi
Good luck
It may be useful package list-to-tree
Install:
bower install list-to-tree --save
or
npm install list-to-tree --save
For example, have list:
var list = [
{
id: 1,
parent: 0
}, {
id: 2,
parent: 1
}, {
id: 3,
parent: 1
}, {
id: 4,
parent: 2
}, {
id: 5,
parent: 2
}, {
id: 6,
parent: 0
}, {
id: 7,
parent: 0
}, {
id: 8,
parent: 7
}, {
id: 9,
parent: 8
}, {
id: 10,
parent: 0
}
];
Use package list-to-tree:
var ltt = new LTT(list, {
key_id: 'id',
key_parent: 'parent'
});
var tree = ltt.GetTree();
Result:
[{
"id": 1,
"parent": 0,
"child": [
{
"id": 2,
"parent": 1,
"child": [
{
"id": 4,
"parent": 2
}, {
"id": 5, "parent": 2
}
]
},
{
"id": 3,
"parent": 1
}
]
}, {
"id": 6,
"parent": 0
}, {
"id": 7,
"parent": 0,
"child": [
{
"id": 8,
"parent": 7,
"child": [
{
"id": 9,
"parent": 8
}
]
}
]
}, {
"id": 10,
"parent": 0
}];
I've written a test script to evaluate the performance of the two most general solutions (meaning that the input does not have to be sorted beforehand and that the code does not depend on third party libraries), proposed by users shekhardtu (see answer) and FurkanO (see answer).
http://playcode.io/316025?tabs=console&script.js&output
FurkanO's solution seems to be the fastest.
/*
** performance test for https://stackoverflow.com/questions/18017869/build-tree-array-from-flat-array-in-javascript
*/
// Data Set (e.g. nested comments)
var comments = [{
id: 1,
parent_id: null
}, {
id: 2,
parent_id: 1
}, {
id: 3,
parent_id: 4
}, {
id: 4,
parent_id: null
}, {
id: 5,
parent_id: 4
}];
// add some random entries
let maxParentId = 10000;
for (let i=6; i<=maxParentId; i++)
{
let randVal = Math.floor((Math.random() * maxParentId) + 1);
comments.push({
id: i,
parent_id: (randVal % 200 === 0 ? null : randVal)
});
}
// solution from user "shekhardtu" (https://stackoverflow.com/a/55241491/5135171)
const nest = (items, id = null, link = 'parent_id') =>
items
.filter(item => item[link] === id)
.map(item => ({ ...item, children: nest(items, item.id) }));
;
// solution from user "FurkanO" (https://stackoverflow.com/a/40732240/5135171)
const createDataTree = dataset => {
let hashTable = Object.create(null)
dataset.forEach( aData => hashTable[aData.id] = { ...aData, children : [] } )
let dataTree = []
dataset.forEach( aData => {
if( aData.parent_id ) hashTable[aData.parent_id].children.push(hashTable[aData.id])
else dataTree.push(hashTable[aData.id])
} )
return dataTree
};
/*
** lets evaluate the timing for both methods
*/
let t0 = performance.now();
let createDataTreeResult = createDataTree(comments);
let t1 = performance.now();
console.log("Call to createDataTree took " + Math.floor(t1 - t0) + " milliseconds.");
t0 = performance.now();
let nestResult = nest(comments);
t1 = performance.now();
console.log("Call to nest took " + Math.floor(t1 - t0) + " milliseconds.");
//console.log(nestResult);
//console.log(createDataTreeResult);
// bad, but simple way of comparing object equality
console.log(JSON.stringify(nestResult)===JSON.stringify(createDataTreeResult));
After many tries I came up with this:
const arrayToTree = (arr, parent = 0) => arr .filter(item => item.parent === parent).map(child => ({ ...child, children: arrayToTree(arr, child.index) }));
const entries = [
{
index: 1,
parent: 0
},
{
index: 2,
parent: 1
},
{
index: 3,
parent: 2
},
{
index: 4,
parent: 2
},
{
index: 5,
parent: 4
},
{
index: 6,
parent: 5
},
{
index: 7,
parent: 6
},
{
index: 8,
parent: 7
},
{
index: 9,
parent: 8
},
{
index: 10,
parent: 9
},
{
index: 11,
parent: 7
},
{
index: 13,
parent: 11
},
{
index: 12,
parent: 0
}
];
const arrayToTree = (arr, parent = 0) => arr .filter(item => item.parent === parent) .map(child => ({ ...child, children: arrayToTree(arr, child.index) })); console.log(arrayToTree(entries));
UPDATE 2022
This is a proposal for unordered items. This function works with a single loop and with a hash table and collects all items with their id. If a root node is found, then the object is added to the result array.
const
getTree = (data, root) => {
const t = {};
data.forEach(o => ((t[o.parentId] ??= {}).children ??= []).push(Object.assign(t[o.id] ??= {}, o)));
return t[root].children;
},
data = { People: [{ id: "12", parentId: "0", text: "Man", level: "1", children: null }, { id: "6", parentId: "12", text: "Boy", level: "2", children: null }, { id: "7", parentId: "12", text: "Other", level: "2", children: null }, { id: "9", parentId: "0", text: "Woman", level: "1", children: null }, { id: "11", parentId: "9", text: "Girl", level: "2", children: null }], Animals: [{ id: "5", parentId: "0", text: "Dog", level: "1", children: null }, { id: "8", parentId: "5", text: "Puppy", level: "2", children: null }, { id: "10", parentId: "13", text: "Cat", level: "1", children: null }, { id: "14", parentId: "13", text: "Kitten", level: "2", children: null }] },
result = Object.fromEntries(Object
.entries(data)
.map(([k, v]) => [k, getTree(v, '0')])
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I like #WilliamLeung's pure JavaScript solution, but sometimes you need to make changes in existing array to keep a reference to object.
function listToTree(data, options) {
options = options || {};
var ID_KEY = options.idKey || 'id';
var PARENT_KEY = options.parentKey || 'parent';
var CHILDREN_KEY = options.childrenKey || 'children';
var item, id, parentId;
var map = {};
for(var i = 0; i < data.length; i++ ) { // make cache
if(data[i][ID_KEY]){
map[data[i][ID_KEY]] = data[i];
data[i][CHILDREN_KEY] = [];
}
}
for (var i = 0; i < data.length; i++) {
if(data[i][PARENT_KEY]) { // is a child
if(map[data[i][PARENT_KEY]]) // for dirty data
{
map[data[i][PARENT_KEY]][CHILDREN_KEY].push(data[i]); // add child to parent
data.splice( i, 1 ); // remove from root
i--; // iterator correction
} else {
data[i][PARENT_KEY] = 0; // clean dirty data
}
}
};
return data;
}
Exapmle:
https://jsfiddle.net/kqw1qsf0/17/
Convert nodes Array to Tree
ES6 function to convert an Array of nodes (related by parent ID) - to a Tree structure:
/**
* Convert nodes list related by parent ID - to tree.
* #syntax getTree(nodesArray [, rootID [, propertyName]])
*
* #param {Array} arr Array of nodes
* #param {integer} id Defaults to 0
* #param {string} p Property name. Defaults to "parent_id"
* #returns {Object} Nodes tree
*/
const getTree = (arr, p = "parent_id") => arr.reduce((o, n) => {
if (!o[n.id]) o[n.id] = {};
if (!o[n[p]]) o[n[p]] = {};
if (!o[n[p]].nodes) o[n[p]].nodes= [];
if (o[n.id].nodes) n.nodes= o[n.id].nodes;
o[n[p]].nodes.push(n);
o[n.id] = n;
return o;
}, {});
Generate HTML List from nodes Tree
Having our Tree in place, here's a recursive function to build the UL > LI Elements:
/**
* Convert Tree structure to UL>LI and append to Element
* #syntax getTree(treeArray [, TargetElement [, onLICreatedCallback ]])
*
* #param {Array} tree Tree array of nodes
* #param {Element} el HTMLElement to insert into
* #param {function} cb Callback function called on every LI creation
*/
const treeToHTML = (tree, el, cb) => el.append(tree.reduce((ul, n) => {
const li = document.createElement('li');
if (cb) cb.call(li, n);
if (n.nodes?.length) treeToHTML(n.nodes, li, cb);
ul.append(li);
return ul;
}, document.createElement('ul')));
Demo time
Here's an example having a linear Array of nodes and using both the above functions:
const getTree = (arr, p = "parent_id") => arr.reduce((o, n) => {
if (!o[n.id]) o[n.id] = {};
if (!o[n[p]]) o[n[p]] = {};
if (!o[n[p]].nodes) o[n[p]].nodes = [];
if (o[n.id].nodes) n.nodes = o[n.id].nodes;
o[n[p]].nodes.push(n);
o[n.id] = n;
return o;
}, {});
const treeToHTML = (tree, el, cb) => el.append(tree.reduce((ul, n) => {
const li = document.createElement('li');
if (cb) cb.call(li, n);
if (n.nodes?.length) treeToHTML(n.nodes, li, cb);
ul.append(li);
return ul;
}, document.createElement('ul')));
// DEMO TIME:
const nodesList = [
{id: 10, parent_id: 4, text: "Item 10"}, // PS: Order does not matters
{id: 1, parent_id: 0, text: "Item 1"},
{id: 4, parent_id: 0, text: "Item 4"},
{id: 3, parent_id: 5, text: "Item 3"},
{id: 5, parent_id: 4, text: "Item 5"},
{id: 2, parent_id: 1, text: "Item 2"},
];
const myTree = getTree(nodesList)[0].nodes; // Get nodes of Root (0)
treeToHTML(myTree, document.querySelector("#tree"), function(node) {
this.textContent = `(${node.parent_id} ${node.id}) ${node.text}`;
this._node = node;
this.addEventListener('click', clickHandler);
});
function clickHandler(ev) {
if (ev.target !== this) return;
console.clear();
console.log(this._node.id);
};
<div id="tree"></div>
Array elements can be in a chaotic order
let array = [
{ id: 1, data: 'something', parent_id: null, children: [] },
{ id: 2, data: 'something', parent_id: 1, children: [] },
{ id: 5, data: 'something', parent_id: 4, children: [] },
{ id: 4, data: 'something', parent_id: 3, children: [] },
{ id: 3, data: 'something', parent_id: null, children: [] },
{ id: 6, data: 'something', parent_id: null, children: [] }
]
function buildTree(array) {
let tree = []
for (let i = 0; i < array.length; i++) {
if (array[i].parent_id) {
let parent = array.filter(elem => elem.id === array[i].parent_id).pop()
parent.children.push(array[i])
} else {
tree.push(array[i])
}
}
return tree
}
const tree = buildTree(array)
console.log(tree);
.as-console-wrapper { min-height: 100% }
var data = [{"country":"india","gender":"male","type":"lower","class":"X"},
{"country":"china","gender":"female","type":"upper"},
{"country":"india","gender":"female","type":"lower"},
{"country":"india","gender":"female","type":"upper"}];
var seq = ["country","type","gender","class"];
var treeData = createHieArr(data,seq);
console.log(treeData)
function createHieArr(data,seq){
var hieObj = createHieobj(data,seq,0),
hieArr = convertToHieArr(hieObj,"Top Level");
return [{"name": "Top Level", "parent": "null",
"children" : hieArr}]
function convertToHieArr(eachObj,parent){
var arr = [];
for(var i in eachObj){
arr.push({"name":i,"parent":parent,"children":convertToHieArr(eachObj[i],i)})
}
return arr;
}
function createHieobj(data,seq,ind){
var s = seq[ind];
if(s == undefined){
return [];
}
var childObj = {};
for(var ele of data){
if(ele[s] != undefined){
if(childObj[ele[s]] == undefined){
childObj[ele[s]] = [];
}
childObj[ele[s]].push(ele);
}
}
ind = ind+1;
for(var ch in childObj){
childObj[ch] = createHieobj(childObj[ch],seq,ind)
}
return childObj;
}
}
this is what i used in a react project
// ListToTree.js
import _filter from 'lodash/filter';
import _map from 'lodash/map';
export default (arr, parentIdKey) => _map(_filter(arr, ar => !ar[parentIdKey]), ar => ({
...ar,
children: _filter(arr, { [parentIdKey]: ar.id }),
}));
usage:
// somewhere.js
import ListToTree from '../Transforms/ListToTree';
const arr = [
{
"id":"Bci6XhCLZKPXZMUztm1R",
"name":"Sith"
},
{
"id":"C3D71CMmASiR6FfDPlEy",
"name":"Luke",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
},
{
"id":"aS8Ag1BQqxkO6iWBFnsf",
"name":"Obi Wan",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
},
{
"id":"ltatOlEkHdVPf49ACCMc",
"name":"Jedi"
},
{
"id":"pw3CNdNhnbuxhPar6nOP",
"name":"Palpatine",
"parentCategoryId":"Bci6XhCLZKPXZMUztm1R"
}
];
const response = ListToTree(arr, 'parentCategoryId');
output:
[
{
"id":"Bci6XhCLZKPXZMUztm1R",
"name":"Sith",
"children":[
{
"id":"pw3CNdNhnbuxhPar6nOP",
"name":"Palpatine",
"parentCategoryId":"Bci6XhCLZKPXZMUztm1R"
}
]
},
{
"id":"ltatOlEkHdVPf49ACCMc",
"name":"Jedi",
"children":[
{
"id":"C3D71CMmASiR6FfDPlEy",
"name":"Luke",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
},
{
"id":"aS8Ag1BQqxkO6iWBFnsf",
"name":"Obi Wan",
"parentCategoryId":"ltatOlEkHdVPf49ACCMc"
}
]
}
]```
I had similar issue couple days ago when have to display folder tree from flat array. I didn't see any solution in TypeScript here so I hope it will be helpful.
In my cases main parent were only one, also rawData array don't have to be sorted. Solutions base on prepare temp object like
{parentId: [child1, child2, ...] }
example raw data
const flatData: any[] = Folder.ofCollection([
{id: '1', title: 'some title' },
{id: '2', title: 'some title', parentId: 1 },
{id: '3', title: 'some title', parentId: 7 },
{id: '4', title: 'some title', parentId: 1 },
{id: '5', title: 'some title', parentId: 2 },
{id: '6', title: 'some title', parentId: 5 },
{id: '7', title: 'some title', parentId: 5 },
]);
def of Folder
export default class Folder {
public static of(data: any): Folder {
return new Folder(data);
}
public static ofCollection(objects: any[] = []): Folder[] {
return objects.map((obj) => new Folder(obj));
}
public id: string;
public parentId: string | null;
public title: string;
public children: Folder[];
constructor(data: any = {}) {
this.id = data.id;
this.parentId = data.parentId || null;
this.title = data.title;
this.children = data.children || [];
}
}
SOLUTION: Function that returns tree structure for flat argument
public getTree(flatData: any[]): Folder[] {
const addChildren = (item: Folder) => {
item.children = tempChild[item.id] || [];
if (item.children.length) {
item.children.forEach((child: Folder) => {
addChildren(child);
});
}
};
const tempChild: any = {};
flatData.forEach((item: Folder) => {
const parentId = item.parentId || 0;
Array.isArray(tempChild[parentId]) ? tempChild[parentId].push(item) : (tempChild[parentId] = [item]);
});
const tree: Folder[] = tempChild[0];
tree.forEach((base: Folder) => {
addChildren(base);
});
return tree;
}
I wrote an ES6 version based on #Halcyon answer
const array = [
{
id: '12',
parentId: '0',
text: 'one-1'
},
{
id: '6',
parentId: '12',
text: 'one-1-6'
},
{
id: '7',
parentId: '12',
text: 'one-1-7'
},
{
id: '9',
parentId: '0',
text: 'one-2'
},
{
id: '11',
parentId: '9',
text: 'one-2-11'
}
];
// Prevent changes to the original data
const arrayCopy = array.map(item => ({ ...item }));
const listToTree = list => {
const map = {};
const roots = [];
list.forEach((v, i) => {
map[v.id] = i;
list[i].children = [];
});
list.forEach(v => (v.parentId !== '0' ? list[map[v.parentId]].children.push(v) : roots.push(v)));
return roots;
};
console.log(listToTree(arrayCopy));
The principle of this algorithm is to use "map" to establish an index relationship. It is easy to find "item" in the list by "parentId", and add "children" to each "item", because "list" is a reference relationship, so "roots" will Build relationships with the entire tree.
Based on #FurkanO's answer, I created another version that does not mutate the origial data (like #Dac0d3r requested). I really liked #shekhardtu's answer, but realized it had to filter through the data many times. I thought a solution could be to use FurkanO's answer by copying the data first. I tried my version in jsperf, and the results where unfortunately (very) bleak... It seems like the accepted answer is really a good one! My version is quite configurable and failsafe though, so I share it with you guys anyway; here is my contribution:
function unflat(data, options = {}) {
const { id, parentId, childrenKey } = {
id: "id",
parentId: "parentId",
childrenKey: "children",
...options
};
const copiesById = data.reduce(
(copies, datum) => ((copies[datum[id]] = datum) && copies),
{}
);
return Object.values(copiesById).reduce(
(root, datum) => {
if ( datum[parentId] && copiesById[datum[parentId]] ) {
copiesById[datum[parentId]][childrenKey] = [ ...copiesById[datum[parentId]][childrenKey], datum ];
} else {
root = [ ...root, datum ];
}
return root
}, []
);
}
const data = [
{
"account": "10",
"name": "Konto 10",
"parentAccount": null
},{
"account": "1010",
"name": "Konto 1010",
"parentAccount": "10"
},{
"account": "10101",
"name": "Konto 10101",
"parentAccount": "1010"
},{
"account": "10102",
"name": "Konto 10102",
"parentAccount": "1010"
},{
"account": "10103",
"name": "Konto 10103",
"parentAccount": "1010"
},{
"account": "20",
"name": "Konto 20",
"parentAccount": null
},{
"account": "2020",
"name": "Konto 2020",
"parentAccount": "20"
},{
"account": "20201",
"name": "Konto 20201",
"parentAccount": "2020"
},{
"account": "20202",
"name": "Konto 20202",
"parentAccount": "2020"
}
];
const options = {
id: "account",
parentId: "parentAccount",
childrenKey: "children"
};
console.log(
"Hierarchical tree",
unflat(data, options)
);
With the options parameter, it is possible to configure what property to use as id or parent id. It is also possible to configure the name of the children property, if someone wants "childNodes": [] or something.
OP could simply use default options:
input.People = unflat(input.People);
If the parent id is falsy (null, undefined or other falsy values) or the parent object does not exist, we consider the object to be a root node.
My solution:
Allows bi-directional mapping (root to leaves and leaves to root)
Returns all nodes, roots, and leaves
One data pass and very fast performance
Vanilla Javascript
/**
*
* #param data items array
* #param idKey item's id key (e.g., item.id)
* #param parentIdKey item's key that points to parent (e.g., item.parentId)
* #param noParentValue item's parent value when root (e.g., item.parentId === noParentValue => item is root)
* #param bidirectional should parent reference be added
*/
function flatToTree(data, idKey, parentIdKey, noParentValue = null, bidirectional = true) {
const nodes = {}, roots = {}, leaves = {};
// iterate over all data items
for (const i of data) {
// add item as a node and possibly as a leaf
if (nodes[i[idKey]]) { // already seen this item when child was found first
// add all of the item's data and found children
nodes[i[idKey]] = Object.assign(nodes[i[idKey]], i);
} else { // never seen this item
// add to the nodes map
nodes[i[idKey]] = Object.assign({ $children: []}, i);
// assume it's a leaf for now
leaves[i[idKey]] = nodes[i[idKey]];
}
// put the item as a child in parent item and possibly as a root
if (i[parentIdKey] !== noParentValue) { // item has a parent
if (nodes[i[parentIdKey]]) { // parent already exist as a node
// add as a child
(nodes[i[parentIdKey]].$children || []).push( nodes[i[idKey]] );
} else { // parent wasn't seen yet
// add a "dummy" parent to the nodes map and put the item as its child
nodes[i[parentIdKey]] = { $children: [ nodes[i[idKey]] ] };
}
if (bidirectional) {
// link to the parent
nodes[i[idKey]].$parent = nodes[i[parentIdKey]];
}
// item is definitely not a leaf
delete leaves[i[parentIdKey]];
} else { // this is a root item
roots[i[idKey]] = nodes[i[idKey]];
}
}
return {roots, nodes, leaves};
}
Usage example:
const data = [{id: 2, parentId: 0}, {id: 1, parentId: 2} /*, ... */];
const { nodes, roots, leaves } = flatToTree(data, 'id', 'parentId', 0);
ES6 Map version :
getTreeData = (items) => {
if (items && items.length > 0) {
const data = [];
const map = {};
items.map((item) => {
const id = item.id; // custom id selector !!!
if (!map.hasOwnProperty(id)) {
// in case of duplicates
map[id] = {
...item,
children: [],
};
}
});
for (const id in map) {
if (map.hasOwnProperty(id)) {
let mappedElem = [];
mappedElem = map[id];
/// parentId : use custom id selector for parent
if (
mappedElem.parentId &&
typeof map[mappedElem.parentId] !== "undefined"
) {
map[mappedElem.parentId].children.push(mappedElem);
} else {
data.push(mappedElem);
}
}
}
return data;
}
return [];
};
/// use like this :
const treeData = getTreeData(flatList);
Incase anyone needs it for multiple parent. Refer id 2 which has multiple parents
const dataSet = [{
"ID": 1,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady"
},
{"ID": 2,
"Phone": "(403) 125-2552",
"City": "Coevorden",
"Name": "Grady"
},
{
"ID": 3,
"parentID": [1,2],
"Phone": "(979) 486-1932",
"City": "Chełm",
"Name": "Scarlet"
}];
const expectedDataTree = [
{
"ID":1,
"Phone":"(403) 125-2552",
"City":"Coevorden",
"Name":"Grady",
"childNodes":[{
"ID":2,
"parentID":[1,3],
"Phone":"(979) 486-1932",
"City":"Chełm",
"Name":"Scarlet",
"childNodes":[]
}]
},
{
"ID":3,
"parentID":[],
"Phone":"(403) 125-2552",
"City":"Coevorden",
"Name":"Grady",
"childNodes":[
{
"ID":2,
"parentID":[1,3],
"Phone":"(979) 486-1932",
"City":"Chełm",
"Name":"Scarlet",
"childNodes":[]
}
]
}
];
const createDataTree = dataset => {
const hashTable = Object.create(null);
dataset.forEach(aData => hashTable[aData.ID] = {...aData, childNodes: []});
const dataTree = [];
dataset.forEach(Datae => {
if (Datae.parentID && Datae.parentID.length > 0) {
Datae.parentID.forEach( aData => {
hashTable[aData].childNodes.push(hashTable[Datae.ID])
});
}
else{
dataTree.push(hashTable[Datae.ID])
}
});
return dataTree;
};
window.alert(JSON.stringify(createDataTree(dataSet)));
I used #FurkanO answer and made a generic function that can be used with any object type, I also wrote this function in TypeScript which i love it more because of auto completions.
Implementation:
1. Javascript:
export const flatListToTree = (flatList, idPath, parentIdPath, childListPath, isParent) => {
const rootParents = [];
const map = {};
for (const item of flatList) {
if (!item[childListPath]) item[childListPath] = [];
map[item[idPath]] = item;
}
for (const item of flatList) {
const parentId = item[parentIdPath];
if (isParent(item)) {
rootParents.push(item);
} else {
const parentItem = map[parentId];
parentItem[childListPath].push(item);
}
}
return rootParents;
};
2. TypeScript: I've assumed the "T" type has a property for children List, you can change 'childListPath' to be a string instead of "keyof T" if you have different use case.
export const flatListToTree = <T>(
flatList: T[],
idPath: keyof T,
parentIdPath: keyof T,
childListPath: keyof T,
isParent: (t: T) => boolean,
) => {
const rootParents: T[] = [];
const map: any = {};
for (const item of flatList) {
if (!(item as any)[childListPath]) (item as any)[childListPath] = [];
map[item[idPath]] = item;
}
for (const item of flatList) {
const parentId = item[parentIdPath];
if (isParent(item)) {
rootParents.push(item);
} else {
const parentItem = map[parentId];
parentItem[childListPath].push(item);
}
}
return rootParents;
};
How to use:
const nodes = [
{ id: 2, pid: undefined, children: [] },
{ id: 3, pid: 2 },
{ id: 4, pid: 2 },
{ id: 5, pid: 4 },
{ id: 6, pid: 5 },
{ id: 7, pid: undefined },
{ id: 8, pid: 7 },
];
const result = flatListToTree(nodes, "id", "pid", "children", node => node.pid === undefined);
Here's a simple helper function that I created modeled after the above answers, tailored to a Babel environment:
import { isEmpty } from 'lodash'
export default function unflattenEntities(entities, parent = {id: null}, tree = []) {
let children = entities.filter( entity => entity.parent_id == parent.id)
if (!isEmpty( children )) {
if ( parent.id == null ) {
tree = children
} else {
parent['children'] = children
}
children.map( child => unflattenEntities( entities, child ) )
}
return tree
}
also do it with lodashjs(v4.x)
function buildTree(arr){
var a=_.keyBy(arr, 'id')
return _
.chain(arr)
.groupBy('parentId')
.forEach(function(v,k){
k!='0' && (a[k].children=(a[k].children||[]).concat(v));
})
.result('0')
.value();
}
Here is a modified version of Steven Harris' that is plain ES5 and returns an object keyed on the id rather than returning an array of nodes at both the top level and for the children.
unflattenToObject = function(array, parent) {
var tree = {};
parent = typeof parent !== 'undefined' ? parent : {id: 0};
var childrenArray = array.filter(function(child) {
return child.parentid == parent.id;
});
if (childrenArray.length > 0) {
var childrenObject = {};
// Transform children into a hash/object keyed on token
childrenArray.forEach(function(child) {
childrenObject[child.id] = child;
});
if (parent.id == 0) {
tree = childrenObject;
} else {
parent['children'] = childrenObject;
}
childrenArray.forEach(function(child) {
unflattenToObject(array, child);
})
}
return tree;
};
var arr = [
{'id':1 ,'parentid': 0},
{'id':2 ,'parentid': 1},
{'id':3 ,'parentid': 1},
{'id':4 ,'parentid': 2},
{'id':5 ,'parentid': 0},
{'id':6 ,'parentid': 0},
{'id':7 ,'parentid': 4}
];
tree = unflattenToObject(arr);
This is a modified version of the above that works with multiple root items, I use GUIDs for my ids and parentIds so in the UI that creates them I hard code root items to something like 0000000-00000-00000-TREE-ROOT-ITEM
var tree = unflatten(records, "TREE-ROOT-ITEM");
function unflatten(records, rootCategoryId, parent, tree){
if(!_.isArray(tree)){
tree = [];
_.each(records, function(rec){
if(rec.parentId.indexOf(rootCategoryId)>=0){ // change this line to compare a root id
//if(rec.parentId == 0 || rec.parentId == null){ // example for 0 or null
var tmp = angular.copy(rec);
tmp.children = _.filter(records, function(r){
return r.parentId == tmp.id;
});
tree.push(tmp);
//console.log(tree);
_.each(tmp.children, function(child){
return unflatten(records, rootCategoryId, child, tree);
});
}
});
}
else{
if(parent){
parent.children = _.filter(records, function(r){
return r.parentId == parent.id;
});
_.each(parent.children, function(child){
return unflatten(records, rootCategoryId, child, tree);
});
}
}
return tree;
}
Copied from the Internet
http://jsfiddle.net/stywell/k9x2a3g6/
function list2tree(data, opt) {
opt = opt || {};
var KEY_ID = opt.key_id || 'ID';
var KEY_PARENT = opt.key_parent || 'FatherID';
var KEY_CHILD = opt.key_child || 'children';
var EMPTY_CHILDREN = opt.empty_children;
var ROOT_ID = opt.root_id || 0;
var MAP = opt.map || {};
function getNode(id) {
var node = []
for (var i = 0; i < data.length; i++) {
if (data[i][KEY_PARENT] == id) {
for (var k in MAP) {
data[i][k] = data[i][MAP[k]];
}
if (getNode(data[i][KEY_ID]) !== undefined) {
data[i][KEY_CHILD] = getNode(data[i][KEY_ID]);
} else {
if (EMPTY_CHILDREN === null) {
data[i][KEY_CHILD] = null;
} else if (JSON.stringify(EMPTY_CHILDREN) === '[]') {
data[i][KEY_CHILD] = [];
}
}
node.push(data[i]);
}
}
if (node.length == 0) {
return;
} else {
return node;
}
}
return getNode(ROOT_ID)
}
var opt = {
"key_id": "ID", //节点的ID
"key_parent": "FatherID", //节点的父级ID
"key_child": "children", //子节点的名称
"empty_children": [], //子节点为空时,填充的值 //这个参数为空时,没有子元素的元素不带key_child属性;还可以为null或者[],同理
"root_id": 0, //根节点的父级ID
"map": { //在节点内映射一些值 //对象的键是节点的新属性; 对象的值是节点的老属性,会赋值给新属性
"value": "ID",
"label": "TypeName",
}
};
You can use npm package array-to-tree https://github.com/alferov/array-to-tree.
It's convert a plain array of nodes (with pointers to parent nodes) to a nested data structure.
Solves a problem with conversion of retrieved from a database sets of data to a nested data structure (i.e. navigation tree).
Usage:
var arrayToTree = require('array-to-tree');
var dataOne = [
{
id: 1,
name: 'Portfolio',
parent_id: undefined
},
{
id: 2,
name: 'Web Development',
parent_id: 1
},
{
id: 3,
name: 'Recent Works',
parent_id: 2
},
{
id: 4,
name: 'About Me',
parent_id: undefined
}
];
arrayToTree(dataOne);
/*
* Output:
*
* Portfolio
* Web Development
* Recent Works
* About Me
*/
You can use this "treeify" package from Github here or NPM.
Installation:
$ npm install --save-dev treeify-js

How to add or remove children under parent array using react state?

I am working on a solution where I have a deep array of parent having child elements
Here is how the array look like
[
{
"id": "1",
"Name": "John Doe",
"children":
[
{
"id": "1.1",
"name": "John doe 1.1"
},
{
"id:": "1.2",
"name:": "John doe 1.2"
},
{
"id": "1.3",
"name": "John doe 1.3",
"children":
[
{
"id": "1.3.1",
"name": "John doe 1.3.1"
}
]
}
]
},
{
"id": "2",
"Name": "Apple",
"children":
[
{
"id": "2.1",
"name": "Apple 2.1"
},
{
"id:": "1.2",
"name:": "Apple 1.2"
}
]
}
]
basically, I have a functionality where I have a table whenever the user clicks on a row I want to add children related to that row,
For example, whenever I click on the row with id 1, I call click function by passing row as an argument, find an index for row and append children under that along with maintaining state, my solution works only for one level nested child, suppose if I want to add children property under children it's not working
Here is the function that I wrote
const expandRow = (row) => {
const index = _(this.state.data)
.thru(function(coll) {
return _.union(coll, _.map(coll, 'children') || []);
})
.flattenDeep()
.findIndex({ id: row.id });
console.log(index)
if (index !== -1) {
let prevState = [...this.state.data];
let el = _(prevState)
.thru(function(coll) {
return _.union(coll, _.map(coll, 'children') || []);
})
.flattenDeep()
.find({ id: row.id });
console.log(el)
el.children = [
{ id: '_' + Math.random().toString(36).substr(2, 5), name: "sfsdfds1", isExpanded:false,parentId:row.id },
{ id: '_' + Math.random().toString(36).substr(2, 5), name: "sfsdfds2",isExpanded:false,parentId:row.id },
];
this.setState({data:[...this.state.data],prevState},()=>{console.log(this.state.data)})
}
updateState(row.id, { isExpanded: true });
};
I also want to maintain state along with it so whenever the user adds a new row my component re-render.
You need recursive function for this.below is the code I write in VueJs for parent child deep array. please take a look hope it's provide you some idea.
and one more thing my data structure is same as your.
let treeData= {
id:1,
type: 0,
status: 0,
parent_id:0,
children: [{
id:1,
type: 0,
status: 0,
parent_id:1,
children:[
{
id:1,
type: 0,
status: 0,
parent_id:1,
}
]
}],
}
ChangeCheckStatus(treedata, item, status) {
for (let i = 0; i < treedata.length; i++) {
if (treedata[i].id === item.id) {
treedata[i].selectAll = status;
return;
}
this.ChangeCheckStatus(treedata[i].children, item, status);
}
}
makeTreeViewThroughCsvData(csvData) {
const data = this.csvToJSON(csvData)
this.rows_new = [...this.rows_new, ...data];
this.rows_new.forEach((_data) => {
let newNode = {}
for (const key in _data) {
if (_data.hasOwnProperty(key)) {
newNode[key.trim()] = _data[key]
}
}
newNode['children'] = []
newNode['status'] = _data.status
/* eslint-disable */
newNode = rest
//variable hold new tree data
this.treeData.push(newNode)
})
this.generateFinalTreeData();
},
generateFinalTreeData() {
const root = []
const nodeIds = []
const mapping = {}
this.treeData.forEach(node => {
// No parentId means Node
if (node.parent_id != undefined) {
//increment NODE ID only when parent_is is not undefined
nodeIds.push(node.id)
}
if (node.parent_id == 0 || node.parent_id == 1) return root.push(node);
// Insert node as child of parent
let parentKey = mapping[node.parent_id];
if (typeof parentKey !== "number") {
parentKey = this.treeData.findIndex(el => el.id === node.parent_id);
mapping[node.parent_id] = parentKey;
}
if (!this.treeData[parentKey].children) {
return this.treeData[parentKey].children = [node];
}
this.treeData[parentKey].children.push(node);
});
this.finalTreeData = root
//vuex commit statement == Redux dispach
this.$store.commit('setTreeViewData', root);
this.$store.commit('setMaxNodeId', Math.max(...nodeIds) + 1);
}

Filtering array of object using key in Javascript (node.js)

Please anyone can help, I want to filter data based on key in node.js, I've tried most of things, but didn't worked.
Below example data which needs to be filtered. I could able to filter key:value pair, nested Json object but filtering Array of json object is not working.
E.g. var data =
[{
"createdBy":"Tom"
"logistics" : 0,
"paymentStatus" : "Paid",
"orderAmount" : 5393.75,
"details" : {
"street": "S.S road",
"postCOde": "440111",
},
"subOrders" : [
{
"name" : "sub Product1",
"mrp" : 12,
"details": "desk"
},
{
"name" : "subProduct2",
"mrp" : 89,
"details": "chair"
}
]
}]
result object should be filtered based on given permission array
var permissionArray = ['logistics','paymentStatus','details.street','subOrders.name','subOrders.details'];
filtered result should look like
{
result = [{
"logistics" : 0,
"paymentStatus" : "Paid",
"details" : {
"street": "S.S road",
},
"subOrders" : [
{
"name" : "sub Product1",
"details" : "desk"
},
{
"name" : "sub Product1",
"details" : "chair"
}
]
}]
}
Building on #radulle's answer to allow the 'details.name' dot notation desired:
(Also updated to now allow multiple subfields of one field - like 'subOrders.name' and 'subOrders.details')
var arr =
[{
"createdBy":"Tom",
"logistics" : 0,
"paymentStatus" : "Paid",
"orderAmount" : 5393.75,
"details" : {
"street": "S.S road",
"postCode": "440111"
},
"subOrders" : [
{
"name" : "sub Product1",
"mrp" : 12,
"details": "desk"
},
{
"name" : "subProduct2",
"mrp" : 89,
"details": "chair"
}
]
}]
var permissionArray = ['logistics','paymentStatus','details.street', 'details.postCode','subOrders.name', 'subOrders.details'];
var arrFiltered = arr.map(el => {
let newEl = {}
for (let elm of permissionArray){
if (elm.includes('.')){
//console.log(elm);
const split_arr = elm.split('.')
//console.log(el[split_arr[0]]);
if (el[split_arr[0]] instanceof Array){
if (!newEl.hasOwnProperty([split_arr[0]]) ){
newEl[split_arr[0]] = el[split_arr[0]].map((child,index) => ({[split_arr[1]]: child[split_arr[1]]}) )
} else {
el[split_arr[0]].forEach((child,index) => {
//console.log(child[ split_arr[1] ]);
newEl[split_arr[0]][index][split_arr[1]] = child[split_arr[1]];
})
}
} else{
if (!newEl.hasOwnProperty([split_arr[0]]) ){
newEl[split_arr[0]] = {[split_arr[1]]: el[split_arr[0]][split_arr[1]]}
} else {
newEl[split_arr[0]][split_arr[1]] = el[split_arr[0]][split_arr[1]];
}
}
} else {
newEl[elm] = el[elm];
}
}
return newEl;
}
)
console.log(arrFiltered)
.as-console-wrapper { max-height: 100% !important; top: 0; }
NOTE: you need to check first if a dot is included with elm.includes('.'), then you have to split it into its two parts with elm.split('.'), then finally you need to check if the value of the key is an array, like 'subOrders' or if it is just an object like 'details' and handle each case with either directly calling the child key, or mapping over the array and calling the child key.
With the permission array as:
var permissionArray = ['logistics','paymentStatus','details.street', 'details.postCode','subOrders.name', 'subOrders.details'];
The output is:
[
{
"logistics": 0,
"paymentStatus": "Paid",
"details": {
"street": "S.S road",
"postCode": "440111"
},
"subOrders": [
{
"name": "sub Product1",
"details": "desk"
},
{
"name": "subProduct2",
"details": "chair"
}
]
}
]
NOTE: upon request of the OP, this now also allows you to have two subfields of one field like 'subOrders.name', 'subOrders.details'
For this !newEl.hasOwnProperty([split_arr[0]]) is used to check if the property/key doesn't already exist. If it does not, then create it, but if it does, then modify this existing property/key's value/item.
You could take a two step approach where you group same keys in an array and store the nested properties as well to the grouped parents.
This is important if you have same parent keys, like
['subOrders.name', 'subOrders.mrp']
For example a grouped array looks like this:
[
[
"logistics"
],
[
"paymentStatus"
],
[
"details",
["street"]
],
[
"subOrders",
["name", "mrp"]
]
]
Then build new objects based on the key and nested keys.
function filter(keys) {
var pathes = keys.reduce((r, path) => {
var [key, ...rest] = path.split('.'),
temp = r.find(([q]) => q === key);
if (rest.length)
if (temp) temp[1].push(rest.join('.'));
else r.push([key, [rest.join('.')]]);
else
if (!temp) r.push([key]);
return r;
}, []);
return function (object) {
const keys = pathes.filter(([key]) => key in object);
return Object.fromEntries(keys.map(([key, rest]) => [
key,
rest
? Array.isArray(object[key])
? object[key].map(filter(rest))
: filter(rest)(object[key])
: object[key]
]));
};
}
var permissionArray = ['foo', 'logistics', 'paymentStatus', 'details.street', 'subOrders.name'],
data = [{ createdBy: "Tom", logistics: 0, paymentStatus: "Paid", orderAmount: 5393.75, details: { street: "S.S road", postCOde: "440111", }, subOrders: [{ name: "sub Product1", mrp: 12 }, { name: "subProduct2", mrp: 89 }] }],
result = data.map(filter(permissionArray));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Instead of building a filtered output, we may operate on original obj and remove its props.
The permissions represent a tree (or trie if node is a word (. delimited))
If we further assume that any specified path is accessible in data, then any leaf has for parent an array or object (and so forth).
We then build a tree, and for any node traverse data and remove the keys which are not part of our specification.
const data = {"createdBy":"Tom","logistics":0,"paymentStatus":"Paid","orderAmount":5393.75,"details":{"street":"S.S road","postCOde":"440111"},"subOrders":[{"name":"sub Product1","mrp":12},{"name":"subProduct2","mrp":89}],"a":{"b":[{"c":3,"d":"a"},{"c":4,"e":"f"}]}}
const d = JSON.parse(JSON.stringify(data)) // do not mutate original
const v = ['logistics','paymentStatus','details.street','subOrders.name','subOrders.details','a.b.c']
const tree = v.map(x => x.split('.')).reduce((o, p) => {
p.reduce((c, x) => (c[x] = c[x] || {}), o)
return o
},{})
function rec (keep, node) {
if (Object.keys(keep).length === 0) return
if (Array.isArray(node)) { return node.forEach(rec.bind(0, keep)) }
for (const k in node) {
!(k in keep) ? delete node[k] : rec(keep[k], node[k])
}
}
rec(tree, d)
console.log(JSON.stringify(d,null,2))
You can maybe do it like this:
var arr =
[{
"createdBy":"Tom",
"logistics" : 0,
"paymentStatus" : "Paid",
"orderAmount" : 5393.75,
"details" : {
"street": "S.S road",
"postCOde": "440111"
},
"subOrders" : [
{
"name" : "sub Product1",
"mrp" : 12
},
{
"name" : "subProduct2",
"mrp" : 89
}
]
}]
var permissionArray = ['logistics','paymentStatus','details']
var arrFiltered = arr.map(el => {
let newEl = {}
for (let elm of permissionArray) newEl[elm] = el[elm]
return newEl
}
)
console.log(arrFiltered)
In the following solution, we divide the handling of objects and plain arrays.
We build-up a "qualified key" corresponding to the equivalent string in the permissions array, as we recursively descend into an object along its properties.
If we hit a primitive value, then we end the recursion for that property.
A new object sanitized is created for each level of the recursion to contain only the permitted properties.
const data = [{ "createdBy": "Tom", "logistics": 0, "paymentStatus": "Paid", "orderAmount": 5393.75, "details": { "street": "S.S road", "postCOde": "440111" }, "subOrders": [{ "name": "sub Product1", "mrp": 12 }, { "name": "subProduct2", "mrp": 89 }] }]
const isPrimitive = (x)=>x === null || typeof x !== "object"
function sanitize(o, permissions, prefix='') {
if (isPrimitive(o)) return o
const sanitized = {}
for (const[k,v] of Object.entries(o)) {
const qk = `${prefix}${k}`
if (permissions.some((p)=>p.match(new RegExp(`^${qk}`)))) {
sanitized[k] = Array.isArray(v)
? sanitizeArray(v, permissions, `${qk}.`)
: sanitize(v, permissions, `${qk}.`)
}
}
return sanitized
}
const sanitizeArray = (arr,permissions,prefix='')=>arr.map((el)=>sanitize(el, permissions, prefix))
const permissions = ['logistics', 'paymentStatus', 'details.street', 'subOrders.name']
const sanitized = sanitizeArray(data, permissions)
console.log(sanitized)

Combining arrays of objects by matching values

I want to combine two arrays of objects, to make it easier for me to display in HTML. The function should find matching values of keys called "id" in arr1, and "source" in arr2. Here's what it looks like:
let arr1 = [
{id = 1,
name = "Anna"},
{id = 2,
name = "Chris"}
]
let arr2 = [
{childName = "Brian",
{source = 1}},
{childName = "Connie",
{source = 2}}
{childName = "Dory",
{source = 1}}
]
I tried different approaches, with best one being using forEach and filter on the arrays. I'm trying to set up a new property in arr1 objects called "children".
arr1.forEach(el => el.children = arr2.filter(checkMatch));
function checkMatch(child){
for(let i=0;i<arr1.length;i++){
child.childName.source === arr1[i].id
}
}
And this results in adding appropriate children to the first object(Anna has Brian and Dory now) so it's correct, but it also adds the same children to the second object (so Chris has also Brian and Dory).
Where is my mistake here? I'm guessing that the loop doesn't work the way I want it to work, but I don't know which one and how it happens.
Since your syntax for creating the objects of arr1 and arr2 are not valid i tried to guess the structure of your objects.
let arr1 = [
{
id: 1,
name: "Anna"
},
{
id: 2,
name: "Chris"
}
];
let arr2 = [
{
childName: "Brian",
source: 1
},
{
childName: "Connie",
source: 2
},
{
childName: "Dory",
source: 1
}
];
arr2.map((child) => {
for (let parent of arr1) {
if (parent.id == child.source) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(child);
}
}
});
console.log(arr1);
There were problems with your JSON, but I tidied and here is option using map and filter
const arr1 = [{
id: 1,
name: "Anna"
},
{
id: 2,
name: "Chris"
}];
const arr2 = [{
childName: "Brian",
parent: {
source: 1
}
},
{
childName: "Connie",
parent: {
source: 2
}
},
{
childName: "Dory",
parent: {
source: 1
}
}];
let merge = arr1.map(p => {
p.children = arr2.filter(c => c.parent.source === p.id).map(c => c.childName);
return p;
});
console.log(merge);
Your json have some problems you should use
:
instead of
=
Also some Braces make the structure incorrect, but I think what you want to do here is fill a children sub array with the childNames of the subject here is my approach:
var json =
[
{
"id" : 1,
"name" : "Anna"
},
{
"id" : 2,
"name" : "Chris"
}
];
var subJson = [
{
"childName" : "Brian",
"source" : 1
},
{
"childName" : "Connie",
"source" : 2
},
{"childName" : "Dory",
"source" : 1
}
];
var newJson = [];
$.each(json,function(index1,item){
newJson.push({"id":item.id,"name":item.name, "children": []});
$.each(subJson,function(index2,subitem){
if(subitem.source == item.id){
newJson[newJson.length - 1].children.push({"childName":subitem.childName}) ;
}
})
})
console.log(newJson);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Hope it helps
The below uses Map for storage and convenient lookup of parents.
const parents = [
{
id: 1,
name: "Anna"
},
{
id: 2,
name: "Chris"
}
]
const children = [
{
childName: "Brian",
source: 1
},
{
childName: "Connie",
source: 2
},
{
childName: "Dory",
source: 1
}
]
// Create a map for easy lookup of parents.
const parentMap = new Map()
// Add parents to the map, based on their id.
parents.forEach(parent => parentMap.set(parent.id, parent))
// Add children to their parents.
children.forEach((child) => {
// Get the parent from the map.
const parent = parentMap.get(child.source)
// Handle parent not found error.
if (!parent) { return console.error('parent not found for', child.childName)}
// Create the children array if it doesn't already exist.
parent.children = parent.children || []
// Add the child to the parent's children array.
parent.children.push(child)
})
// Output the result.
Array.from(parentMap).forEach(parent => console.log(parent[1]))
Result:
{
id: 1,
name: 'Anna',
children: [
{ childName: 'Brian', source: 1 },
{ childName: 'Dory', source: 1 }
]
}
{
id: 2,
name: 'Chris',
children: [
{ childName: 'Connie', source: 2 }
]
}

Push object keys and its values to array

I have an object like this:
{
"id": 23,
"name": "Jacob",
"link": {
"rel": "self",
"link": "www.abc.com"
},
"company":{
"data":{
"id": 1,
"ref": 324
}
}
I want to store each key with its value to an array in javascript or typescript like this
[["id":23], ["name":"Jacob"], ["link":{......, ......}]] and so on
I am doing this so that I can append an ID for each.
My best guess I would loop through the array and append an ID/a flag for each element, which I don't know how to do as well.... how to address this issue ? thanks
var arr = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var innerObj = {};
innerObj[prop] = obj[prop];
arr.push(innerObj)
}
}
console.log(arr);
here is demo https://plnkr.co/edit/9PxisCVrhxlurHJYyeIB?p=preview
p.forEach( function (country) {
country.forEach( function (entry) {
entry.push( {"value" : 'Greece', "synonyms" : 'GR'});
});
});
you can try to use experimental Object.entries:
let obj = {
"id": 23,
"name": "Jacob",
"link": {
"rel": "self",
"link": "www.abc.com"
},
"company":{
"data":{
"id": 1,
"ref": 324
}
}};
console.log(Object.entries(obj).map(item => ({[item[0]]:item[1]})));
for unsupported browsers you can use polyfill: https://github.com/es-shims/Object.entries
You could use an iterative/recursive approach with the object and their nested parts. It works for any depths.
function getKeyValue(object) {
return Object.keys(object).reduce(function (result, key) {
return result.concat(
object[key] && typeof object[key] === 'object' ?
getKeyValue(object[key]) :
[[key, object[key]]]
);
}, []);
}
var data = { id: 23, name: "Jacob", link: { rel: "self", link: "www.abc.com" }, company: { data: { id: 1, ref: 324 } } };
console.log(getKeyValue(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use the Object.keys method to get an array of the keys, then use the Array#map method to return a new array containing individual objects for each property.
This ES6 one-liner should do it:
const splitObject = o => Object.keys(o).map(e => ({ [e]: o[e] }));
Or in ES5:
function splitObject(o) {
return Object.keys(o).map(function(e) {
return Object.defineProperty({}, e, {
value: o[e],
enumerable: true
});
});
}
var res = [];
_.transform( {
"id": 23,
"name": "Jacob",
"link": {
"rel": "self",
"link": "www.abc.com"
},
"company": {
"data": {
"id": 1,
"ref": 324
}
}
}, function(result, value, key) {
res.push(key +':'+value);
}, {});
You can use underscore
Supported in all major browser, including IE11
Object.entries() gives you exactly this.
const obj = {
id: 23,
name: 'Jacob',
link: {
rel: 'self',
link: 'www.abc.com'
},
company: {
data: {
id: 1,
ref: 324
}
}
};
Object.entries(obj);
// output:
[
[
"id",
23
],
[
"name",
"Jacob"
],
[
"link",
{
"rel": "self",
"link": "www.abc.com"
}
],
[
"company",
{
"data": {
"id": 1,
"ref": 324
}
}
]
]
var obj=[{"Name":ABC,"Count":123},{"Name":XYZ,"Count":456}];
var arr = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var innerObj = {};
innerObj[0] = obj[prop];
arr.push(innerObj[0]);
}
}
/* Here above exmple innerobj index set to 0 then we will get same data into arr if u not menstion then arr will conatins arr[0] our result.
then we need to call first record obj arr[0][0] like this*/
const foo = { "bar": "foobar", "foo": "foobar" }
Object.entries(foo)
should result in:
[["bar", "foobar"], ["foo", "foobar"]]
maybe there's a function to pass to convert all commas to colons
Here's the documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries

Categories