I'm trying to change the structure of a json by removing duplicate keys. Otherwise, to put the children of a same name inside only one name node.
Current JSON:
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "AgglomerativeCluster",
"size": [
"3938"
]
}
]
}
]
},
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "CommunityStructure",
"size": [
"3812"
]
}
]
}
]
}
]
}
Desired output:
{
"name": "flare",
"children": [
{
"name": "analytics",
"children": [
{
"name": "cluster",
"children": [
{
"name": "AgglomerativeCluster",
"size": 3938
},
{
"name": "CommunityStructure",
"size": 3812
}
]
}
]
}
]
};
Thanks for your help.
Typically, StackOverflow isn't the place to have people write code for you, and your question should be more specific as to with what part of your algorithm you are having trouble. However, this looked fun, so I did it.
I solved this by first converting it to an object whose properties are the names and values are the children/size. This insured that each named instance was grouped with other named instances.
var mutate = function(desired, current) {
for (var x = 0; x < current.length; x++) {
if (Object.hasOwnProperty.call(current[x], 'size')) {
desired[current[x].name] = parseInt(current[x].size[0], 10);
}
else {
if (!Object.hasOwnProperty.call(desired, current[x].name)) {
desired[current[x].name] = Object.create(null);
}
mutate(desired[current[x].name], current[x].children);
}
}
return desired;
};
I then converted that back to your original desired format by iterating over the Object.entries (key/value pairs).
var mutate2 = function(current) {
var desired = [];
var entries = Object.entries(current);
for (var x = 0; x < entries.length; x++) {
var o = Object.create(null);
o.name = entries[x][0];
if (typeof entries[x][1] === 'number') {
o.size = entries[x][1];
}
else {
o.children = mutate2(entries[x][1]);
}
desired.push(o);
}
return desired;
};
You get your result by using this hideous beast:
var desiredJson = mutate2(mutate(Object.create(null), [ currentJson ]));
console.log(desiredJson);
Related
I have a JSON array of the following format (this data is pulled from mongodb to be displayed as a tree graph on a react-based front-end):
[
{
"name": "7654321",
"children": [
{
"_id": "LjYgocn9PsHhEFbM7",
"accountId": "4343213"
},
{
"_id": "sB2ipCstYnLnHrAuu",
"accountId": "4343271"
},
{
"_id": "JhugmhxS7A57Y34wM",
"accountId": "4343276"
}
]
},
{
"name": "4343213",
"children": [
]
},
{
"name": "4343271",
"children": [
{
"_id": "sie9mtttgdRw7Ktma",
"accountId": "4343279"
}
]
},
{
"name": "4343279",
"children": [
{
"_id": "sie23mtttgdRw7Ktma",
"accountId": "8765345"
}
]
},
{
"name": "4343276",
"children": [
]
}
]
The goal is to re-format (rename and delete some keys) this data to be used in react-tree-graph. From the sample above, output should look like:
[
{
"name": "7654321",
"children": [
{
"name": "4343213"
},
{
"name": "4343271",
"children": [
{
"name": "4343279",
"children": [
{
"name": "8765345"
}
]
}
]
},
{
"name": "4343276"
}
]
}
]
Any help is appreciated!
You could first create a Map that has as keys the name property values, and as corresponding values the (unfinished) result objects. They start off with just the name property.
Then you can iterate the children information in the input, to wire the children into the above mentioned result objects, which can be done efficiently using the name as key in the Map.
Whenever you wire a child object into a parent object, you know that child is not a top-level object in the final result. So starting with all nodes, you would trim that list (a Set) all those nodes that occur in a children array. This will leave you with only the top level nodes, which in its array form represents the desired output.
Implementation:
let data = [{"name": "7654321","children": [{"_id": "LjYgocn9PsHhEFbM7","accountId": "4343213"},{"_id": "sB2ipCstYnLnHrAuu","accountId": "4343271"},{"_id": "JhugmhxS7A57Y34wM","accountId": "4343276"}]},{"name": "4343213","children": []},{"name": "4343271","children": [{"_id": "sie9mtttgdRw7Ktma","accountId": "4343279"}]},{"name": "4343279","children": [{"_id": "sie23mtttgdRw7Ktma","accountId": "8765345"}]},{"name": "4343276","children": []}];
let map = new Map(data.map(({name, children}) => [name, { name }]));
let roots = new Set(map.values());
for (let {name, children} of data) {
if (!children?.length) continue;
map.get(name).children = children.map(({accountId}) => {
let child = map.get(accountId) || { name: accountId };
roots.delete(child);
return child;
});
}
let result = Array.from(roots);
console.log(result);
I have an array of data that looks like this:
{
"data": [
{
"id": "20200722_3",
"eventDate": "2020-07-22T00:00:00",
"eventName": "Football",
"eventDetails": [
"Men's First Round (2 matches)"
],
"eventVenue": "Venue A"
},
{
"id": "20200722_1",
"eventDate": "2020-07-22T00:00:00",
"eventName": "Football",
"eventDetails": [
"Men's First Round (2 matches)"
],
"eventVenue": "Venue B"
}
]
}
Now I wanted to group the data by multiple properties. For example, by eventDate, eventName, eventDetails, and eventVenue. Which I've done with this code referenced from this post:
const groupBy = (array, attrs) => {
var output = [];
for (var i = 0; i < array.length; ++i) {
var ele = array[i];
var groups = output;
for (var j = 0; j < attrs.length; ++j) {
var attr = attrs[j];
var value = ele[attr];
var gs = groups.filter(g => {
return g.hasOwnProperty('label') && g['label'] === value;
});
if (gs.length === 0) {
var g = {};
if (isArray.g['label'] ) {
}
g['label'] = value;
g['groups'] = [];
groups.push(g);
groups = g['groups'];
} else {
groups = gs[0]['groups'];
}
}
groups.push(ele);
}
return output;
}
var result = groupBy(data, ['eventDate', 'eventName', 'eventDetails', 'eventVenue'])
Which results in an array like this:
[{
"label": "2020-07-23T00:00:00",
"groups": [{
"label": "Football",
"groups": [{
"label": [
"Men's First Round (2 matches)"
],
"groups": [{
"label": "Venue A",
"groups": [
"Object"
]
}]
},
{
"label": [
"Men's First Round (2 matches)"
],
"groups": [{
"label": "Venue B",
"groups": [
"Object"
]
}]
}
}]
}]
}]
You can see that for the output above, there are two separate "groups" that have the label "Men's First Round (2 matches)". I'm trying to figure out how I can combine these objects that have duplicate value ? I'm looking for something that would output like this:
[{
"label": "2020-07-23T00:00:00",
"groups": [{
"label": "Football",
"groups": [{
"label": [
"Men's First Round (2 matches)"
],
"groups": [{
"label": "Venue A",
"groups": [
"Object"
]
},
{
"label": "Venue B",
"groups": [
"Object"
]
}]
}]
}]
}]
Any help would be greatly appreciated.
I'll share the answer that I came up with for those that are curious.
For my needs, I know that if the array with the eventName only contained 1 attribute, it could be a duplicate. So in order to fix that, I converted the array that only had 1 value to a string:
if (ele[attr] && ele[attr].length === 1) {
var value = ele[attr].toString();
} else {
var value = ele[attr];
}
I wrote this script to convert a nested array with the structure below to a nested object with parent child relationships.
list = [
['lvl-1 item-1', 'lvl-2 item-1'],
['lvl-1 item-1', 'lvl-2 item-1', 'lvl-3 item-1'],
['lvl-1 item-1', 'lvl-2 item-1', 'lvl-3 item-2'],
['lvl-1 item-2', 'lvl-2 item-1', 'lvl-3 item-1'],
['lvl-1 item-2', 'lvl-2 item-2', 'lvl-3 item-2', 'lvl-4 item-1'],
];
It seems to do the trick, but in order to prime the script I've had to add data.children wrapper around the initial data structure. I'm not convinced it is needed, though I haven't been able to workout how to get rid of it.
Can anyone see anything I'm missing?
console.log(nestedArrayToJson(list));
function nestedArrayToJson(structure) {
const top_item = '0';
// This was added to behave like the child data structure.
let data = {
children: [
{
name: top_item,
parent: null,
children: [],
}],
};
for(let i = 0; i < structure.length; i++) {
let parents = [top_item];
for(let j = 0; j < structure[i].length; j++) {
let obj = data;
for(parent of parents) {
obj = obj.children.find(o => o.name === parent);
}
const name = structure[i][j];
if(!obj.children.find(o => o.name === name)) {
obj.children.push({
name,
parent,
children: [],
});
}
parents.push(structure[i][j]);
}
}
return data.children[0];
}
Sample Output
{
"name": "0",
"parent": null,
"children": [
{
"name": "lvl-1 item-1",
"parent": "0",
"children": [
{
"name": "lvl-2 item-1",
"parent": "lvl-1 item-1",
"children": [
{
"name": "lvl-3 item-1",
"parent": "lvl-2 item-1",
"children": []
},
{
"name": "lvl-3 item-2",
"parent": "lvl-2 item-1",
"children": []
}
]
}
]
},
{
"name": "lvl-1 item-2",
"parent": "0",
"children": [
{
"name": "lvl-2 item-1",
"parent": "lvl-1 item-2",
"children": [
{
"name": "lvl-3 item-1",
"parent": "lvl-2 item-1",
"children": []
}
]
},
{
"name": "lvl-2 item-2",
"parent": "lvl-1 item-2",
"children": [
{
"name": "lvl-3 item-2",
"parent": "lvl-2 item-2",
"children": [
{
"name": "lvl-4 item-1",
"parent": "lvl-3 item-2",
"children": []
}
]
}
]
}
]
}
]
}
The for loops can be cleaned up by extracting some functionality to named functions.
const node = (name, parent = null) => ({name, parent, children: []}) handles creating a node.
Nodes can then be added with addNode()
To search for the current next parent node findNamedNode()
If a node with the current name is found it moves down to the next node. If no node exists with the current name it is created.
function createTree(arr, topItem = 'Top') {
const node = (name, parent = null) => ({name, parent, children: []});
const addNode = (parent, child) => {
parent.children.push(child);
return child;
};
const findNamedNode = (name, parent) => {
for(const child of parent.children) {
if(child.name === name) { return child; }
const found = findNamedNode(name, child);
if(found) { return found; }
}
};
const top = node(topItem);
let current;
for(const children of arr) {
current = top;
for(const name of children) {
const found = findNamedNode(name, current);
current = found ? found : addNode(current,
node(name, current.name));
}
}
return top;
}
Thanks to the help from #Blindman67 on Code Review.
https://codereview.stackexchange.com/questions/219418/convert-nested-array-of-values-to-a-tree-structure/
I am building a JSON file dynamically. I want to add a JSON array in the JSON object. The JSON looks like-
{"name":"Root",
"children":[
{"name":"child1"},
{"name":"child2"}
]}
Now, I want to add -
[{"name":"child11"},{"name":"child12"}]
under "child1" object. How to do it? I have also tried keeping blank children object while creating the original JSON object, but JSON parser doesn't keep those empty children block. In current scenario, when I am using push() function to add new child it throws exception. Any help is much appreciated.
Thanks in advance!!
Edit1: I think I didn't made myself clear enough. I have researched SO before posting this question, and I guess this is not a duplicate question. My target JSON is -
{
"name": "Root",
"children": [{
"name": "child1",
"children": [{
{"name": "child11"},
{"name": "child12"}
}]
},
{
"name": "child2",
"children": [{
{"name": "child21"},
{"name": "child22"}
}]
}
]
};
Here is the code snippet that I am trying to run -
flare = {
"name": "Root",
"children": [{
"name": "child1",
"children": [{
{"name": "child11"},
{"name": "child12"}
}]
},
{
"name": "child2",
"children": [{
{"name": "child21"},
{"name": "child22"}
}]
}
]
};
var updatedJson = twoLevelSelection(flare);
function twoLevelSelection(json){
var root = flare.name;
var string_json = '';
string_json = '{"name": "'+root+'","children": [';
flare.children.forEach(
function(d){
string_json = string_json+ '{"name":"'+d.name+'","children":[]},';
}
);
string_json = string_json.substring(0,string_json.length-1);
string_json = string_json + ']}';
return JSON.parse(string_json);
}
// data is the original data.i.e - flare
// d is the clicked node, under which children to be added
function traverse(data,d){
var queue = [];
var next = data;
while(next){
if(next.children){
next.children.forEach(
function(k){
queue.push(k);
}
)
}
if(queue[0].name==d.name){
alert(queue[0].children);
//d.children = queue[0].children;
var child_string='';
var child_array = [];
queue[0].children.forEach(
function(j){
child_string = '{"name": "'+j.name+'"}';
child_array.push(child_string);
}
);
console.log(child_array);
d.children = [...child_array];
console.log(updatedJson);
//update(updatedJson);
break;
}else{
next= queue.shift();
}
}
}
The traverse() will be called on a click event.
Sorry, for not providing clarity at first place. Thanks!
You can use the Spread Operator to accomplish that.
This code snippet has a function called addElements which find the target and adds the new elements to the children array.
var obj = {
"name": "Root",
"children": [{
"name": "child1"
},
{
"name": "child2"
}
]
};
var newArray = [
{ "name": "child11"},
{ "name": "child12"}
];
var addElements = function(target, array) {
obj.children.forEach(function(child) {
if (child.name === target) {
child['children'] = [...(child['children'] || []), ...newArray];
return;
}
});
};
addElements('child1', newArray);
console.log(obj);
See? now your obj.childre[0].children array contains the new elements.
I am having a problem with my algorithm in javascript. Basically, I am building a graph and I need to keep reference on subgraph. The problem is that from one iteration to other, I loose this reference and I still unable to understand while this behavior. This is my code.
<!DOCTYPE html>
<html>
<body>
<script>
const SIZE = 1000;
var graph = {
"name": "flare",
"children": [ ]
}
var testjson = {
"angular-chart.js#1.0.3": {
"chart.js#2.3.0": {
"chartjs-color#2.0.0": {
"chartjs-color-string#0.4.0": {
"color-name#1.1.1": {}
},
"color-convert#0.5.3": {}
},
"moment#2.15.2": {}
},
"angular#1.5.8": {}
}
};
function traverse_it(obj, myFunc){
var ref = graph;// .children;
for(var prop in obj){
if( !Object.keys(obj[prop]).length){
console.log("{}");
ref.push({'name': prop, 'size': SIZE });
return;
}
else if(typeof obj[prop]==='object'){
console.log("------------------------------");
console.log(typeof ref);
console.log('ref in the ginning of iteration');
console.log(ref);
ref = ref.children;
console.log(ref);
ref.push({'name': prop, 'children': [] });
console.log(ref);
ref = ref[ref.length -1];
console.log("--------graph----------------");
console.log(graph);
console.log("---------graph----------------");
console.log('ref at the end of iteration');
console.log(ref);
console.log("------------------------------");
if (graph.children.length > 1)
return;
traverse_it(obj[prop]);
}else{
console.log("error ");
throw err;
}
}
return;
}
traverse_it(testjson);
</script>
</body>
</html>
The output is something like this:
------------------------------
object
ref in the ginning of iteration
{
"name": "flare",
"children": []
}
[]
[
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
--------graph----------------
{
"name": "flare",
"children": [
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
}
---------graph----------------
ref at the end of iteration
{
"name": "angular-chart.js#1.0.3",
"children": []
}
------------------------------
------------------------------
object
ref in the ginning of iteration
{
"name": "flare",
"children": [
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
}
[
{
"name": "angular-chart.js#1.0.3",
"children": []
}
]
[
{
"name": "angular-chart.js#1.0.3",
"children": []
},
{
"name": "chart.js#2.3.0",
"children": []
}
]
--------graph----------------
{
"name": "flare",
"children": [
{
"name": "angular-chart.js#1.0.3",
"children": []
},
{
"name": "chart.js#2.3.0",
"children": []
}
]
}
---------graph----------------
ref at the end of iteration
{
"name": "chart.js#2.3.0",
"children": []
}
------------------------------
As you can see in this output, ref is changing from iteration 1 to 2. How can I keep the same value of ref (subgraph) at the end of iteration and begin of new iteration.