Traverse JSON object to build JSTree - javascript

I have a JSON object and want to traverse it so that it matches the structure of JSTree:
Original JSON:
{
"analogBasebandProcessorBoardType": "Test_BOARD",
"tuners": {
"tuner1": "RFE_MAIN",
"tuner2": "MAX93"
},
"allowedSoftConfigs": ["30-16", "30-29", "10-22"]
}
It should look like this:
{
'core': {
'data': [
{
'text': 'analogBasebandProcessorBoardType',
'children':
[
{
'text': 'Test_BOARD'
}
]
},
{
'text': 'tuners',
'children':
[{
'text': 'Tuner1',
'children':
[{
'text': 'RFE_MAIN'
}]
},
{
'text': 'Tuner2',
'children':
[{
'text': 'MAX93'
}]
}
]
},
{
'text': 'allowedSoftConfigs',
'children':
[
{
'text': '30-16'
},
{
'text': '30-29'
},
{
'text': '10-22'
}
]
},
]
}
}
I think the only way to solve this is traversing. I have tried it but it is not exactly what I want, but I think I am not very far away from the solution.
This is the JSON that is being generated:
{
"core": {
"data": {
"analogBasebandProcessorBoardType": {
"text": "analogBasebandProcessorBoardType",
"children": [{
"text": "Test_BOARD"
}],
"tuners": {
"tuner1": {
"text": "tuner1",
"children": [{
"text": "RFE_MAIN"
}],
"tuner2": {
"text": "tuner2",
"children": [{
"text": "MAX93"
}],
"allowedSoftConfigs": {
"0": {
"1": {
"2": {
"text": "2",
"children": [{
"text": "10-22"
}]
},
"text": "1",
"children": [{
"text": "30-29"
}]
},
"text": "0",
"children": [{
"text": "30-16"
}]
}
}
}
}
}
}
}
}
}
My code alway uses the "name" as the key for the data-array. It would be correct if there is no key. But I am not sure where this behavior is caused.
It would be great if someone could take a look on it as I don't know how to solve it.
Here is the complete code but it is easier to view in jsfiddle i think:
//function to add something to objects with a string path
function assign(obj, prop, value) {
if (typeof prop === "string")
prop = prop.split(".");
if (prop.length > 1) {
var e = prop.shift();
assign(obj[e] =
Object.prototype.toString.call(obj[e]) === "[object Object]"
? obj[e]
: {},
prop,
value);
} else
obj[prop[0]] = value;
}
$(function() {
// 6 create an instance when the DOM is ready
var tbjsonstring = '{ "analogBasebandProcessorBoardType": "Test_BOARD", "tuners": { "tuner1": "RFE_MAIN","tuner2": "MAX93" }, "allowedSoftConfigs": ["30-16", "30-29", "10-22"]}';
var tbjson = JSON.parse(tbjsonstring);
var computedJSON = {
'core': {
'data': [
]
}
}
var path = "core.data";
console.log(tbjson);
(function traverse(o) {
var z = 0;
for (var i in o) {
data0 = {
'text': i,
}
data1 = {
'text': o[i],
}
if(traversed == 1){
console.log("traversed" + o[i]);
// assign(computedJSON,path,data1);
traversed = 0;
}else{
// assign(computedJSON,path,data0);
}
console.log('key : ' + i + ', value: ' + o[i]);
//console.log(path);
path = path+"."+i;
z++;
if (o[i] !== null && typeof(o[i])=="object") {
//going on step down in the object tree!!
var traversed = "1";
traverse(o[i]);
}else{
//key value pair, no children
data = {};
data = {
'text': i,
'children': [{
'text': o[i]
}]
}
assign(computedJSON,path,data);
}
}
})
(tbjson);
//print to the console
console.log(JSON.stringify(computedJSON));
//This is the working json, computedJSON should looke like this:
var jstreejson = {
'core': {
'data': [
{
'text': 'analogBasebandProcessorBoardType',
'children':
[
{
'text': 'Test_BOARD'
}
]
},
{
'text': 'tuners',
'children':
[{
'text': 'Tuner1',
'children':
[{
'text': 'RFE_MAIN'
}]
},
{
'text': 'Tuner2',
'children':
[{
'text': 'MAX93'
}]
}
]
},
{
'text': 'allowedSoftConfigs',
'children':
[
{
'text': '30-16'
},
{
'text': '30-29'
},
{
'text': '10-22'
}
]
},
]
}
}
//jstree initialization
$('#jstree').jstree(jstreejson);
$('#tbjstree').jstree(computedJSON);
// 7 bind to events triggered on the tree
$('#jstree').on("changed.jstree", function(e, data) {
console.log(data.selected);
});
});
The won't run in the stackoverflow-snippet-editor (even with loaded js/css files) so please visit jsfiddle for a working demo:
<a href='https://jsfiddle.net/dnffx4g8/6/' target='_blank'>JSFiddle Demo</a>
jsfiddle-link: https://jsfiddle.net/dnffx4g8/6/

You could use an iterative an recursive approach for it, with a function who checks for array and object and iterates accordingly.
function buildObject(source) {
if (Array.isArray(source)) {
return source.reduce(function (r, a) {
if (a !== null && typeof a === 'object') {
return r.concat(buildObject(a));
}
r.push({ text: a });
return r;
}, []);
}
if (source !== null && typeof source === 'object') {
return Object.keys(source).map(function (k) {
return {
text: k,
children: buildObject(source[k])
};
});
}
return [{ text: source }];
}
var data = { "analogBasebandProcessorBoardType": "Test_BOARD", "tuners": { "tuner1": "RFE_MAIN", "tuner2": "MAX93" }, "allowedSoftConfigs": ["30-16", "30-29", "10-22"], "PathValue": [{ "links": ["in1->GSES_1.in1", "GSES_1.out1->GSES_1.in1", "GSES_1.out1->out1_1"], "ParamFile": "IN1_OUT12.txt" }, { "links": ["in1->GSES_1.in1", "GSES_1.out2->GSES_2.in1", "GSES_2.out1->out1_2"], "ParamFile": "IN1_OUT52.txt" }] },
result = { core: { data: buildObject(data) } };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6
function buildObject(source) {
if (Array.isArray(source)) {
return source.map(text => ({ text }));
}
if (source !== null && typeof source === 'object') {
return Object.keys(source).map(text => ({ text, children: buildObject(source[text])}));
}
return [{ text: source }];
}
var object = { analogBasebandProcessorBoardType: "Test_BOARD", tuners: { tuner1: "RFE_MAIN", tuner2: "MAX93" }, allowedSoftConfigs: ["30-16", "30-29", "10-22"] },
result = { core: { data: buildObject(object) } };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Given an object named data, you could use this ES6 function:
var result = {
core: {
data: (function treeify(data) {
return Array.isArray(data) ? data.map ( value => treeify(value) )
: typeof data !== 'object' || data == null ? { text: data }
: Object.keys(data).map(key => ({ text:key, children: [treeify(data[key])] }));
})(data)
}
};
Here is a snippet with your sample data:
var data = {
"analogBasebandProcessorBoardType": "Test_BOARD",
"tuners": {
"tuner1": "RFE_MAIN",
"tuner2": "MAX93"
},
"allowedSoftConfigs": ["30-16", "30-29", "10-22"]
};
var result = {
core: {
data: (function treeify(data) {
return Array.isArray(data) ? data.map ( value => treeify(value) )
: typeof data !== 'object' || data == null ? { text: data }
: Object.keys(data).map ( key => ({ text: key, children: [treeify(data[key])] }) );
})(data)
}
};
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
NB: I would not name your objects tbjson or computedJson, to avoid confusion with plain JavaScript objects. The term JSON is better reserved for the text notation.
Explanation of the Code
treeify is a recursive function. The value it returns depends on the data type of the argument that is passed to it:
When it is an array (Array.isArray()) then the function is called recursively for each element (treeify(value)), and these results are assembled in a new array (the return value of data.map()) that is returned to the caller.
When it is a non-object (as null is considered an object a separate test is needed for that), then the value is given to the text property of a new object which is returned to the caller ({ text: data }).
When it is an object, the keys of that object are iterated (Object.keys(data).map()) and for each corresponding value the function is called recursively (treeify(data[key])). That result is put in an array and assigned to the children property of a new object, while the key is assigned to the text property of that same object. This is returned to the caller.
The result variable will start the calling chain for initialising the value of the data property.

Related

make object structure recursive structure with same keys in javascript

Currently, I have this object
const path ={
"posts": {
"backend": {
"a.mdx": "./pages/posts/backend/a.mdx"
},
"frontend": {},
"retrospective": {
"b.mdx": "./pages/posts/retrospective/b.mdx",
"c.mdx": "./pages/posts/retrospective/c.mdx",
"d.mdx": "./pages/posts/retrospective/d.mdx"
}
}
}
What I want is..
const path = [{
title: 'posts',
sub: [
{
title: 'backend',
sub: [
{ title: 'a.mdx', path: './pages/posts/backend/a.mdx' },
],
},
{
title: 'frontend',
sub: [],
},
{
title: 'retrospective',
sub: [
{ title: 'b.mdx', path: './pages/posts/retrospective/b.mdx' },
{ title: 'c.mdx', path: './pages/posts/retrospective/c.mdx' },
{ title: 'd.mdx', path: './pages/posts/retrospective/d.mdx' },
],
},
],
}];
In this situation, how can I make the structure recursively?
I've read the lodash library document but I couldn't find a good combination to handle this issue.
Can I get any hint for this?
You can create recursive function and run it again if value is object, or set path if it is string. Check inline comments:
// Object
const path = {
"posts": {
"backend": {
"a.mdx": "./pages/posts/backend/a.mdx"
},
"frontend": {},
"retrospective": {
"b.mdx": "./pages/posts/retrospective/b.mdx",
"c.mdx": "./pages/posts/retrospective/c.mdx",
"d.mdx": "./pages/posts/retrospective/d.mdx"
}
}
};
// Recursive function
const recursiveFn = data => {
// Set array for function runtime result
const res = [];
// Iterate through keys in your object
for(const key in data) {
// If value is object, process it
// again in recursion
if(typeof data[key] === 'object' && data[key] !== null) {
res.push({
"title": key,
"sub": recursiveFn(data[key])
});
// If value is string
} else if(typeof data[key] === 'string') {
res.push({
"title": key,
"path": data[key]
});
}
}
// Return result
return res;
}
// Run test
console.log(recursiveFn(path));

Update an element in an array in React (using filter /map)

I have an array which is of this structure:
this.state.mainArray= [{
"Upperelement1": "12345",
"Upperelement2" : [
{
Key1:'ok1',Key2:'ok2',Key3:'ok3'
},
{
Key1:'ok4',Key2:'ok6',Key3:'ok7'
},
]
},
{
"Upperelement1": "6789",
"Upperelement2" : [
{
Key1:'ok8',Key2:'ok9',Key3:'o10'
},
{
Key1:'ok11',Key2:'ok12',Key3:'ok13'
},
]
}
]
Idea is to iterate through the array and find element where Upperelement1 = 12345 and Key1:'ok1' (value of key is unique)
and add another key to Upperelement2, key4. After updation, array will look like:
[{
"Upperelement1": "12345",
"Upperelement2" : [
{
Key1:'ok1',Key2:'ok2',Key3:'ok3',Key4:'somevalue'
},
{
Key1:'ok4',Key2:'ok6',Key3:'ok7'
},
]
},
{
"Upperelement1": "6789",
"Upperelement2" : [
{
Key1:'ok8',Key2:'ok9',Key3:'o10'
},
{
Key1:'ok11',Key2:'ok12',Key3:'ok13'
},
]
}
]
I tried something like way:
mainArray.map(items => if (items. Upperelement1 == '12345')
but that does not work,
Any thoughts how this can be achieved ?
Can we use filter ?
You can use find(), some(), and map() to do that.
var data = [{ Upperelement1: "12345", Upperelement2: [{ Key1: "ok1", Key2: "ok2", Key3: "ok3" }, { Key1: "ok4", Key2: "ok6", Key3: "ok7" }, ] }, { Upperelement1: "6789", Upperelement2: [{ Key1: "ok8", Key2: "ok9", Key3: "o10" }, { Key1: "ok11", Key2: "ok12", Key3: "ok13" }, ] } ];
data.find(item =>
item.Upperelement1 === "12345" &&
item.Upperelement2.some(value => value.Key1 === "ok1")
)
.Upperelement2.map(item => {
if (item.Key1 === "ok1") {
item["Key4"] = "somevalue";
}
return item;
});
console.log(data);
You can do with 2 map
const newValues = mainArray.map((item) => {
if (item.Upperelement1 !== '12345') {
return item
}
return {
...item,
Upperelement2: item.Upperelement2.map(upper => {
if(upper.Key1 !== 'ok1') {
return upper
}
return {
...upper,
Key4:'somevalue',
}
})
}
})
I think it's straightforward, using nested map like this
arr.map(function(item){
if (item.Upperelement1 === '12345'){
return {
Upperelement1: item.Upperelement1,
Upperelement2: item.Upperelement2.map(function(obj){
if (obj.Key1 === 'ok1'){
return Object.assign({}, obj, { Key4: 'somevalue'});
}
return obj;
})
};
}
return item;
})

Group Multiple Objects Based on Type Using lodash

I want to group unordered-list-item and ordered-list-item.
Below is the original structure:
{
data: {
areas: [{
sections: [{
rjf: [
{
type: "unordered-list-item",
text: "Item 1",
},
{
type: "unordered-list-item",
text: "Item 2",
},
{
type: "paragraph",
text: "This is text",
}]
}]
}]
}
}
Now I want to group all list items into a new object.
So the expected structure is:
{
data: {
areas: [{
sections: [{
rjf: [
{
type: "list",
items: [{
type: "unordered-list-item",
text: "Item 1",
},
{
type: "unordered-list-item",
text: "Item 2",
}]
},
{
type: "paragraph",
text: "This is text",
}]
}]
}]
}
}
So I wanted to move all the unordered-list-item and ordered-list-item to items array and the following object type like paragraph shouldn't be impacted.
I created a solution in TypeScript, but the code was too long:
const areas = data.areas;
const listItemTypes = ['unordered-list-item', 'ordered-list-item'];
return areas.map(area => {
return area.sections.map(section => {
let lastHeadingIndex = -1;
return section.rjf.reduce((acc, current, index) => {
if (!current.type || !listItemTypes.includes(current.type)) {
lastHeadingIndex = acc.length;
return [...acc, current];
}
let listObject = acc.find((el, i) => i > lastHeadingIndex && i < index && el.type === 'list');
if (!listObject) {
listObject = {
type: 'list',
items: [current]
};
return [...acc, listObject];
}
listObject.items = [...listObject.items, current];
return acc;
}, []);
});
});
How can I achieve the same functionality using lodash?
****UPDATE****
I tried with lodash, but dosen't seems to work.
var content = {
data: {
areas: [{
sections: [{
rjf: [{
type: "unordered-list-item",
text: "Item 1",
},
{
type: "unordered-list-item",
text: "Item 2",
},
{
type: "paragraph",
text: "This is text",
}
]
}]
}]
}
};
var result = content.data.areas.map((area => {
return area.sections.map(section => {
section.rfj = _.groupBy(section.rfj, 'type');
});
}));
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, ' ') + '</pre>';
<script src="https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js"></script>
return data.areas.map((area => {
return area.sections.map(section => {
return section.rfj = _.groupBy(section.rfj, 'type')
})
})
You may need some more little tweaking on the result if you need a more specific structure, but this should do most of the work
You could simplify your code to something like this.
Use nested map on areas and sections.
You are returrning the arrays from both map. Return objects with sections and rjf property instead (Assuming both structures are objects with just one property)
When looping through rjf, create 2 arrays: items and others
Group each object to these 2 arrays based on whether types array includes the current object's type.
Create an array with one list object ({ type: "list", items }) and remaining objects from others array
const types = ['unordered-list-item', 'ordered-list-item'],
input = {data:{areas:[{sections:[{rjf:[{type:"unordered-list-item",text:"Item 1",},{type:"unordered-list-item",text:"Item 2",},{type:"paragraph",text:"This is text",}]}]}]}}
const areas = input.data.areas.map(a => {
return {
sections: a.sections.map(s => {
const items = [], others = [];
s.rjf.forEach(o =>
types.includes(o.type) ? items.push(o) : others.push(o)
)
return { rjf: [{ type: "list", items }, ...others] }
})
}
})
console.log({ data: { areas } })
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use another approach by copying each object leven and map all arrays and group the last level by using a function for grouping.
This approach uses am iter function which gets all functions for each level and the starting object.
At the end, it returns a new object with the given structure and the grouped items.
function copy(source, fn) {
return Object.assign({}, ...Object
.entries(source)
.map(([k, v]) => ({ [k]: fn(v) }))
);
}
function map(array, fn) {
return array.map(fn);
}
function group(array) {
var items;
return array.reduce((r, o) => {
if (listItemTypes.includes(o.type)) {
if (!items) {
items = [];
r.push({ type: 'list', items });
}
items.push(o);
} else {
items = undefined;
r.push(o);
}
return r;
}, []);
}
function iter([fn, ...fns], object) {
return fn ? fn(object, iter.bind(null, fns)) : object;
}
var object = { data: { areas: [{ sections: [{ rjf: [{ type: "unordered-list-item", text: "Item 1" }, { type: "unordered-list-item", text: "Item 2" }, { type: "paragraph", text: "This is text" }] }] }] } },
listItemTypes = ['unordered-list-item', 'ordered-list-item'],
result = iter([copy, copy, map, copy, map, copy, group], object);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to replace all the values in JSON structure using javascript?

If I have JSON structure like this, I want to parse this JSON replace all values with '{param.keyName}', if the key value is array of objects than need to generate its value like {param.headKey[index]keyName}
{
"resources": [
{
"name": "prod",
"type": "local",
"properties": {
"zone": "asia",
"disks": [
{
"sizeGb": 3,
"diskType": "boot",
"name": "backup"
},
{
"sizeGb": 4,
"diskType": "ssd",
"name": "cache"
}
]
}
}
]
}
The output of the function should be JSON like this where all the values should be replaced with the mapping. If there is any array of objects than it should prefixed by array index like {param.disks0_name}, where disks is an array of objects.
{
"resources": [
{
"name": "prod",
"type": "local",
"properties": {
"zone": "{param.zone}",
"disks": [
{
"sizeGb": '{param.disks0_sizeGb}',
"diskType": '{param.disks0_diskType}',
"name": "{param.disks0_name}"
},
{
"sizeGb": '{param.disks1_zone}',
"diskType": '{param.disks1_diskType}',
"name": "{param.disks1_name}"
}
]
}
}
]
}
You could look for the arrays/objects and iterate them for getting the path for the last property.
Format as desired (which is unclear for nested arrays).
function formatPath(path) {
return `{${path.join('.')}}`;
}
function getPath(object, path = []) {
return Object.assign(
Array.isArray(object) ? [] : {},
...Object.entries(object).map(([k, v]) => ({
[k]: v && typeof v === 'object'
? getPath(v, path.concat(k))
: formatPath(path.concat(k))
}))
);
}
var data = { resources: [{ name: "prod", type: "local", properties: { zone: "asia", disks: [{ sizeGb: 3, diskType: "boot", name: "backup" }, { sizeGb: 4, diskType: "ssd", name: "cache" }] } }] };
data = { resources: data.resources.map(o => Object.assign({}, o, { properties: getPath(o.properties, ['param']) })) };
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES5
function formatPath(path) {
return '{' + path.join('.') + '}';
}
function getPath(object, path) {
path = path || [];
return Object.keys(object).reduce(
function (r, k) {
r[k] = object[k] && typeof object[k] === 'object'
? getPath(object[k], path.concat(k))
: formatPath(path.concat(k));
return r;
},
Array.isArray(object) ? [] : {}
);
}
var data = { resources: [{ name: "prod", type: "local", properties: { zone: "asia", disks: [{ sizeGb: 3, diskType: "boot", name: "backup" }, { sizeGb: 4, diskType: "ssd", name: "cache" }] } }] };
data = { resources: data.resources.map(function (o) {
return Object.keys(o).reduce(function (r, k) {
r[k] = k === 'properties'
? getPath(o.properties, ['param'])
: o[k];
return r;
}, {});
}) };
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Using Array.prototype.filter to filter nested children objects

I have an array of objects similar to the following block of code:
var arr = [
{
text: 'one',
children: [
{
text: 'a',
children: [
{
text: 'something'
}
]
},
{
text: 'b'
},
{
text: 'c'
}
]
},
{
text: 'two'
},
{
text: 'three'
},
{
text: 'four'
}
];
In the above structure, I want to search a string in text property and I need to perform this search over all the children.
For example, if I search for something, the result should be an array of object in the following form:
[
{
children: [
{
children: [
{
text: 'something'
}
]
}
]
}
];
Notice all the text properties that do not match the input string something have been deleted.
I have come up with the following block of code using Array.prototype.filter. However, I can still see extra properties in the result:
function search(arr, str) {
return arr.filter(function(obj) {
if(obj.children && obj.children.length > 0) {
return search(obj.children, str);
}
if(obj.text === str) {
return true;
}
else {
delete text;
return false;
}
});
}
Here is the fiddle link: https://jsfiddle.net/Lbx2dafg/
What am I doing wrong?
I suggest to use Array#forEach, because filter returns an array, which is needed, but not practical for this purpose, because it returns all children with it.
This proposal generates a new array out of the found items, with the wantes item text and children.
The solution works iterative and recursive. It finds all occurences of the search string.
function filter(array, search) {
var result = [];
array.forEach(function (a) {
var temp = [],
o = {},
found = false;
if (a.text === search) {
o.text = a.text;
found = true;
}
if (Array.isArray(a.children)) {
temp = filter(a.children, search);
if (temp.length) {
o.children = temp;
found = true;
}
}
if (found) {
result.push(o);
}
});
return result;
}
var array = [{ text: 'one', children: [{ text: 'a', children: [{ text: 'something' }] }, { text: 'b' }, { text: 'c' }] }, { text: 'two' }, { text: 'three' }, { text: 'four' }];
console.log(filter(array, 'something'));
Your function search returns an array with object from "parent" level, that's why you "still see extra properties in the result".Secondly, this line delete text; doesn't delete an object or object property - it should be delete obj.text;. Here is solution using additional Array.map fuinction:
function search(arr, str) {
return arr.filter(function(obj) {
if (obj.text !== str) {
delete obj.text;
}
if (obj.children && obj.children.length > 0) {
return search(obj.children, str);
}
if (obj.text === str) {
return true;
} else {
delete obj.text;
return false;
}
});
}
var result = search(arr, 'something').map(function(v) { // filtering empty objects
v['children'] = v['children'].filter((obj) => Object.keys(obj).length);
return {'children':v['children'] };
});
console.log(JSON.stringify(result,0,4));
The output:
[
{
"children": [
{
"children": [
{
"text": "something"
}
]
}
]
}
]
https://jsfiddle.net/75nrmL1o/

Categories

Resources