Javascript Nested JSON Parsing - javascript

I have a json object that object contains main nodes and nested nodes. Each node have a property "type", i want to remove the nodes object which contains the property "type = doc".Please find below example json image format. I attached 2 files one image is the input format and other one is the output format.
var json = {
"navigations": [
{
"disp_order": "1",
"menu_id": "25266",
"nodes": [
{
"disp_order": "2",
"menu_id": "18951",
"type": "DOC"
}
],
"type": "MENU"
},
{
"disp_order": "20",
"menu_id": "25204",
"nodes": [
{
"disp_order": "1",
"menu_id": "10295",
"type": "DOC"
},
{
"disp_order": "10",
"menu_id": "25207",
"nodes": [
{
"disp_order": "999",
"menu_id": "17250",
"type": "DOC"
},
],
"type": "MENU"
},
{
"disp_order": "20",
"menu_id": "25209",
"nodes": [
{
"disp_order": "999",
"menu_id": "18881",
"type": "DOC"
},
],
"type": "MENU"
},
],
"type": "MENU"
},
]
}

https://jsfiddle.net/1hoctvdp/
function deleteNonMenu(obj) {
if (obj.type == "DOC") {
return true;
}
if (obj.nodes) {
for (var i = 0; i < obj.nodes.length; i++) {
var res = deleteNonMenu(obj.nodes[i]);
if (res == true) {
delete obj.nodes[i];
}
}
}
return false;
}
for (var i = 0; i < json.navigations.length; i++) {
var result = deleteNonMenu(json.navigations[i]);
if (result == true) {
delete json.navigations[i];
}
}
console.log(json);

Just as an alternative, you could use this ES6 function, which leaves the original object immutable, and creates the filtered version as return value:
function withoutDocNodes(obj) {
return Object(obj) !== obj ? obj // Primitive value: return w/o change
: Object.assign(Array.isArray(obj) ? [] : {}, // Create array or object
// With following properties:
...Object.keys(obj) // For each property:
// Exclude those with DOC type in their value
.filter( key => Object(obj[key]).type !== 'DOC')
// Get recursive value (also without DOC types)
.map( key => [withoutDocNodes(obj[key]), key] )
// Exclude if resulting value is an empty (object or array)
.filter ( ([o]) => Object.keys(o).length )
// Produce key/value to add: arrays get index as property
.map( ([o, key], i) => ({ [Array.isArray(obj) ? i : key]: o }) )
);
}
const obj = {
"navigations": [
{
"disp_order": "1",
"menu_id": "25266",
"nodes": [
{
"disp_order": "2",
"menu_id": "18951",
"type": "DOC"
}
],
"type": "MENU"
},
{
"disp_order": "20",
"menu_id": "25204",
"nodes": [
{
"disp_order": "1",
"menu_id": "10295",
"type": "DOC"
},
{
"disp_order": "10",
"menu_id": "25207",
"nodes": [
{
"disp_order": "999",
"menu_id": "17250",
"type": "DOC"
},
],
"type": "MENU"
},
{
"disp_order": "20",
"menu_id": "25209",
"nodes": [
{
"disp_order": "999",
"menu_id": "18881",
"type": "DOC"
},
],
"type": "MENU"
},
],
"type": "MENU"
},
]
};
const result = withoutDocNodes(obj);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

This one is also working, try this code :
function mainFunction() {
var data = new Array();
data = excludeDocs(json.navigations);
}
function excludeDocs(nodes) {
var _docs = new Array();
$.each(nodes, function(index, node) {
if(typeof node === 'object') {
if(node.type === 'DOC') {
_docs.push(node.menu_id);
}
else if(typeof node.nodes === 'object') {
var _nodes = excludeDocs(node.nodes);
if(!(typeof nodes === 'object' && nodes.length > 0)) {
delete node.nodes;
}
else {
node.nodes = _nodes;
}
}
}
});
return nodes.filter(function(n) {
return !_docs.includes(n.menu_id);
});
}

Here is a solution using object-scan. It's powerful for data processing once you wrap your head around it.
Note: (1) Expects well behaved input and (2) modifies the input in place
// const objectScan = require('object-scan');
const prune = (type, input) => {
objectScan(['**.nodes[*].type'], {
filterFn: ({ value, gparent, gproperty }) => {
if (value === type) {
gparent.splice(gproperty, 1);
}
}
})(input);
};
const json = { navigations: [{ disp_order: '1', menu_id: '25266', nodes: [{ disp_order: '2', menu_id: '18951', type: 'DOC' }], type: 'MENU' }, { disp_order: '20', menu_id: '25204', nodes: [{ disp_order: '1', menu_id: '10295', type: 'DOC' }, { disp_order: '10', menu_id: '25207', nodes: [{ disp_order: '999', menu_id: '17250', type: 'DOC' }], type: 'MENU' }, { disp_order: '20', menu_id: '25209', nodes: [{ disp_order: '999', menu_id: '18881', type: 'DOC' }], type: 'MENU' }], type: 'MENU' }] };
prune('DOC', json);
console.log(json);
// => { navigations: [ { disp_order: '1', menu_id: '25266', nodes: [], type: 'MENU' }, { disp_order: '20', menu_id: '25204', nodes: [ { disp_order: '10', menu_id: '25207', nodes: [], type: 'MENU' }, { disp_order: '20', menu_id: '25209', nodes: [], type: 'MENU' } ], type: 'MENU' } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#16.0.0"></script>
Disclaimer: I'm the author of object-scan

Related

How in JS to merge in one object two json objects where the ID of on object correspond on the same ID of the second object

My question relates to the fact I'm querying 2 different objects from DB and the result is in JSON. I need to merge them into one.
The 2 objects have in common this two key/value IRBId = ... and id = ... and they look as an example
OBJ 1
{
"data":{
"IRBs":{
"nodes":[
{
"id":"8",
"name":"Admin ",
},
{
"id":"9",
"name":"Again",
}
],
}
}
}
OBJ 2
{
"data":{
"informedConsentForms":{
"count":3,
"nodes":[
{
"id":"93",
...
"IRBId":"9",
},
{
"id":"92",
...
"IRBId":"8",
},
{
"id":"91",
...
"IRBId":"8",
}
],
}
},
As you will see above OBJ 2 and OBJ 1 corresponding with the same at IRBid and id.
What I need is to merge the two OBJ where IRBId OBJ 2 === id OBJ 1
The result I would expect after the merge is
OBJ merged
{
[{
"id":"93",
...
"IRBId":"9",
"irb": {
"name":"Again ",
...
}
},
{
"id":"92",
...
"IRBId":"8",
"irb": {
"name":"Admin ",
...
}
},
{
"id":"91",
...
"IRBId":"8",
"irb": {
"name":"Admin ",
...
}
],
},
I don't know how to make it looks like this.
Try using Array.reduce
Logic
Loop through second object data nodes
Find the matching nodes from object 1 data nodes.
Push to accumulator with required details. (I have added only the nodes that was mentioned in in Expected resut, you can add asmuch as you need.)
const obj1 = {
"data": {
"IRBs": {
"nodes": [
{
"id": "8",
"name": "Admin ",
},
{
"id": "9",
"name": "Again",
}
],
}
}
}
const obj2 = {
"data": {
"informedConsentForms": {
"count": 3,
"nodes": [
{
"id": "93",
"IRBId": "9",
},
{
"id": "92",
"IRBId": "8",
},
{
"id": "91",
"IRBId": "8",
}
],
}
},
};
const obj1List = obj1.data.IRBs.nodes;
const output = obj2.data.informedConsentForms.nodes.reduce((acc, curr) => {
const matchingNode = obj1List.find((item) => item.id === curr.IRBId);
if (matchingNode) {
acc.push({
id: curr.id,
IRBId: curr.IRBId,
irb: {
name: matchingNode.name
}
})
}
return acc;
}, []);
console.log(output);
You need to use the map function on the nodes in the first object to construct a new object that contains the second and first object's attributes.
const obj1 = {
"data": {
"IRBs": {
"nodes": [{
"id": "8",
"obj1": "one",
"name": "Admin ",
},
{
"id": "9",
"obj1": "two",
"name": "Again",
}
]
}
}
};
const obj2 = {
"data": {
"informedConsentForms": {
"count": 3,
"nodes": [{
"id": "93",
"obj2": "1",
"IRBId": "9",
},
{
"id": "92",
"obj2": "2",
"IRBId": "8",
},
{
"id": "91",
"obj2": "3",
"IRBId": "8",
}
],
}
}
};
const obj1Data = obj1.data.IRBs.nodes;
const obj2Data = obj2.data.informedConsentForms.nodes;
const res = obj2Data.map(item => {
const obj1Item = obj1Data.find(obj1Item => item.IRBId === obj1Item.id);
return obj1Item ? { ...item, "irb": { ...obj1Item}} : { ...item};
});
console.log(res);
i am using nested loop, try this one
const obj2 = {
"data":{
"informedConsentForms":{
"count":3,
"nodes":[
{
"id":"93",
"IRBId":"9",
},
{
"id":"92",
"IRBId":"8",
},
{
"id":"91",
"IRBId":"8",
}
],
}
},
}
const obj1 = {
"data":{
"IRBs":{
"nodes":[
{
"id":"8",
"name":"Admin ",
},
{
"id":"9",
"name":"Again",
}
],
}
}
}
const result = [];
const obj2Nodes = obj2.data.informedConsentForms.nodes;
for(let i = 0; i < obj2Nodes.length; i++) {
const obj1Nodes = obj1.data.IRBs.nodes
for(let j = 0; j < obj1Nodes.length; j++) {
if(obj2Nodes[i].IRBId === obj1Nodes[j].id) {
const {id, ...reObj1Nodes} = obj1Nodes[j];
result.push({
...obj2Nodes[i],
'irb': {
...reObj1Nodes
}
})
}
}
}
console.log(result)

How to transform this specific js array into js object?

I have one javascript array got from back end api, for convenience, need to be sort into the form of below, described as final target.
But I don't know how to start. Anyone can help?
The original src array is like below :
var src = [
{
"parent_kind" : "Animal",
"name" : "Cow"
},
{
"name" : "Animal"
},
{
"parent_kind" : "Animal",
"name" : "Dog"
},
{
"parent_kind" : "Animal",
"name" : "Horse"
},
{
"name" : "Vehicle"
},
{
"parent_kind" : "Vehicle",
"name" : "Bus"
},
{
"parent_kind" : "Bus",
"name" : "Shuttle"
},
]
The final target is :
{
"Vehicle" : {
"Bus" : {
"Shuttle" : {}
}
},
"Animal" : {
"Cow" : {},
"Dog" : {},
"Horse" : {}
}
}
I can got each element of the original array by
for (let ele of src) {
console.log(ele)
}
you can do that with a simple Array.reduce() method
var src =
[ { parent_kind: 'Animal', name: 'Cow' }
, { name: 'Animal' }
, { parent_kind: 'Animal', name: 'Dog' }
, { parent_kind: 'Animal', name: 'Horse' }
, { name: 'Vehicle' }
, { parent_kind: 'Vehicle', name: 'Bus' }
, { parent_kind: 'Bus', name: 'Shuttle' }
]
let res = src.reduce((a,{parent_kind,name},i)=>
{
if(!!parent_kind)
{
let np = a.p.find(x=>x.pN===parent_kind)
if(!np)
{
a.r[parent_kind] = {}
np = {pN:parent_kind, e:a.r[parent_kind]}
a.p.push( np )
}
let z = np.e[name] = {}
a.p.push( {pN:name, e:z} )
}
return (i===a.len)? a.r : a
}
,{len:src.length-1,r:{},p:[]})
console.log( res )
.as-console-wrapper { max-height: 100% !important; top: 0; }
if you can ok with loop the src many times :-( That's performance is not a concern.
var src = [{
"parent_kind": "Animal",
"name": "Cow"
},
{
"name": "Animal"
},
{
"parent_kind": "Animal",
"name": "Dog"
},
{
"parent_kind": "Animal",
"name": "Horse"
},
{
"name": "Vehicle"
},
{
"parent_kind": "Vehicle",
"name": "Bus"
},
{
"parent_kind": "Bus",
"name": "Shuttle"
},
];
const fn = (source, result, parent) => {
const children = source.filter(({
parent_kind
}) => parent_kind === parent).map(({
name
}) => name);
children.forEach(c => result[c] = {});
children.forEach(c => fn(source, result[c], c));
}
result = {};
fn(src, result)
console.log(result);

how to push array value to another array object value javascript

I've data response like this
{
"data": {
"product": {
"colors": ["#3498db", "#00ccff"],
"items": [
{
"label": "Phone",
"value": "23.00"
},
{
"label": "Notebook",
"value": "3.00"
}
]
}
}
}
and then i want push the colors inside items
expected: items have three(3) variable each of index
items: [
{
label: phone,
value: 23.00,
color: #3498db
}
]
i've try using push and concat but i got error "Cannot read property 'data' of undefined"
here my code
generaliseData(dashboardC) {
let genData = Object.assign({}, dashboardC)
if (genData.product.items.length > 0) {
for (let i of genData.product.items) {
i.value = parseInt(i.value)
for (let j of genData.product.colors) {
i = i.push(j)
}
}
console.log(genData)
}
}
You can use map to iterate through your list, expect to have the length of colors equal the length of item
const response = {
"data": {
"product": {
"colors": ["#3498db", "#00ccff"],
"items": [
{
"label": "Phone",
"value": "23.00"
},
{
"label": "Notebook",
"value": "3.00"
}
]
}
}
};
function addColorToItem(response) {
const product = response.data.product;
const colors = product.colors;
const items = product.items;
return items.map((item, index) => {
item.color = colors[index];
return item;
})
}
console.log(addColorToItem(response));
You could iterate items and assign a color.
var response = { data: { product: { colors: ["#3498db", "#00ccff"], items: [{ label: "Phone", value: "23.00" }, { label: "Notebook", value: "3.00" }] } } },
temp = response.data.product;
temp.items.forEach((o, i) => o.color = temp.colors[i]);
console.log(response);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use a simple forEach() loop for that result:
var data = {
"product": {
"colors": ["#3498db", "#00ccff"],
"items": [{
"label": "Phone",
"value": "23.00"
},
{
"label": "Notebook",
"value": "3.00"
}
]
}
};
data.product.items.forEach((item, index) => item.color = data.product.colors[index]);
console.log(data);

Parse array of objects recursively and filter object based on id

i have this array of objects : getCategory (variable)
[
{
"id": "20584",
"name": "Produits de coiffure",
"subCategory": [
{
"id": "20590",
"name": "Coloration cheveux",
"subCategory": [
{
"id": "20591",
"name": "Avec ammoniaque"
},
{
"id": "20595",
"name": "Sans ammoniaque"
},
{
"id": "20596",
"name": "Soin cheveux colorés"
},
{
"id": "20597",
"name": "Protection"
},
{
"id": "20598",
"name": "Nuancier de couleurs"
}
]
},
{
"id": "20593",
"name": "Soins cheveux",
"subCategory": [
{
"id": "20594",
"name": "Shampooing"
},
{
"id": "20599",
"name": "Après-shampooing"
},
{
"id": "20600",
"name": "Masques"
},
and i tried everything i could search in stackoverflow ..
lets say on this array i want to get recursively and object with the specified id .. like 20596 and it should return
{
"id": "20596",
"name": "Soin cheveux colorés"
}
The logic way i am doing is like this :
var getSubcategory = getCategory.filter(function f(obj){
if ('subCategory' in obj) {
return obj.id == '20596' || obj.subCategory.filter(f);
}
else {
return obj.id == '20596';
}
});
dont know what else to do .
Thanks
PS : I dont use it in browser so i cannot use any library . Just serverside with no other library . find dont work so i can only use filter
You need to return the found object.
function find(array, id) {
var result;
array.some(function (object) {
if (object.id === id) {
return result = object;
}
if (object.subCategory) {
return result = find(object.subCategory, id);
}
});
return result;
}
var data = [{ id: "20584", name: "Produits de coiffure", subCategory: [{ id: "20590", name: "Coloration cheveux", subCategory: [{ id: "20591", name: "Avec ammoniaque" }, { id: "20595", name: "Sans ammoniaque" }, { id: "20596", name: "Soin cheveux colorés" }, { id: "20597", name: "Protection" }, { id: "20598", name: "Nuancier de couleurs" }] }, { id: "20593", name: "Soins cheveux", subCategory: [{ id: "20594", name: "Shampooing" }, { id: "20599", name: "Après-shampooing" }, { id: "20600", name: "Masques" }] }] }];
console.log(find(data, '20596'));
console.log(find(data, ''));

Creating a tree from a flat list using lodash

I am trying to create a category tree using the array of json objects below.
I want to set a category as a child of another category if its parent equals the id of the other, and I want the posts also to be a children of that category instead of having a separate field for posts, I'll add a flag field that if it is a category or not isParent.
It looks like its working alright, but as you may see, if a category has both category and post as child, it'll only show the categories. Another problem with that is if the post has a null value on its array, it will still push them as children.
What are the mistakes in my code, or is there a simpler or better solution to this?
var tree = unflatten(getData());
var pre = document.createElement('pre');
console.log(tree);
pre.innerText = JSON.stringify(tree, null, 4);
document.body.appendChild(pre);
function unflatten(array, parent, tree) {
tree = typeof tree !== 'undefined' ? tree : [];
parent = typeof parent !== 'undefined' ? parent : {
id: 0
};
_.map(array, function(arr) {
_.set(arr, 'isParent', true);
});
var children = _.filter(array, function(child) {
return child.parent == parent.id;
});
if (!_.isEmpty(children)) {
if (parent.id == 0) {
tree = children;
} else {
parent['children'] = children;
}
_.each(children, function(child) {
var posts = _.map(child.posts, function(post) {
return _.set(post, 'isParent', false);
});
child['children'] = posts;
delete child.posts;
unflatten(array, child);
});
}
return tree;
}
function getData() {
return [{
"id": "c1",
"parent": "",
"name": "foo",
"posts": [{
"id": "p1"
}]
}, {
"id": "c2",
"parent": "1",
"name": "bar",
"posts": [{
"id": "p2"
}]
}, {
"id": "c3",
"parent": "",
"name": "bazz",
"posts": [
null
]
}, {
"id": "c4",
"parent": "3",
"name": "sna",
"posts": [{
"id": "p3"
}]
}, {
"id": "c5",
"parent": "3",
"name": "ney",
"posts": [{
"id": "p4"
}]
}, {
"id": "c6",
"parent": "5",
"name": "tol",
"posts": [{
"id": "p5"
}, {
"id": "p6"
}]
}, {
"id": "c7",
"parent": "5",
"name": "zap",
"posts": [{
"id": "p7"
}, {
"id": "p8"
}, {
"id": "p9"
}]
}, {
"id": "c8",
"parent": "",
"name": "quz",
"posts": [
null
]
}, {
"id": "c9",
"parent": "8",
"name": "meh",
"posts": [{
"id": "p10"
}, {
"id": "p11"
}]
}, {
"id": "c10",
"parent": "8",
"name": "ror",
"posts": [{
"id": "p12"
}, {
"id": "p13"
}]
}, {
"id": "c11",
"parent": "",
"name": "gig",
"posts": [{
"id": "p14"
}]
}, {
"id": "c12",
"name": "xylo",
"parent": "",
"posts": [{
"id": "p15"
}]
}, {
"id": "c13",
"parent": "",
"name": "grr",
"posts": [{
"id": "p16"
}, {
"id": "p17"
}, {
"id": "p14"
}, {
"id": "p18"
}, {
"id": "p19"
}, {
"id": "p20"
}]
}]
}
<script src="//cdn.jsdelivr.net/lodash/3.10.1/lodash.min.js"></script>
Expected Output
So the expected output will be more like:
[
{
id: 'c1',
isParent: true,
children: [
{
id: 'c2',
isParent: true,
children: []
},
{
id: 'p1'
isParent: false
}
]
}
]
And so on..
Your code is very imperative. Try focusing on the "big picture" of data flow instead of writing code by trial-and-error. It's harder, but you get better results (and, in fact, usually it's faster) :)
My idea is to first group the categories by their parents. This is the first line of my solution and it actually becomes much easier after that.
_.groupBy and _.keyBy help a lot here:
function makeCatTree(data) {
var groupedByParents = _.groupBy(data, 'parent');
var catsById = _.keyBy(data, 'id');
_.each(_.omit(groupedByParents, ''), function(children, parentId) {
catsById['c' + parentId].children = children;
});
_.each(catsById, function(cat) {
// isParent will be true when there are subcategories (this is not really a good name, btw.)
cat.isParent = !_.isEmpty(cat.children);
// _.compact below is just for removing null posts
cat.children = _.compact(_.union(cat.children, cat.posts));
// optionally, you can also delete cat.posts here.
});
return groupedByParents[''];
}
I recommend trying each part in the developer console, then it becomes easy to understand.
I have made a small fidde that I think that is what you want.
http://jsfiddle.net/tx3uwhke/
var tree = buildTree(getData());
var pre = document.getElementById('a');
var jsonString = JSON.stringify(tree, null, 4);
console.log(jsonString);
pre.innerHTML = jsonString;
document.body.appendChild(pre);
function buildTree(data, parent){
var result = [];
parent = typeof parent !== 'undefined' ? parent : {id:""};
children = _.filter(data, function(value){
return value.parent === parent.id;
});
if(!_.isEmpty(children)){
_.each(children, function(child){
if (child != null){
result.push(child);
if(!_.isEmpty(child.posts)){
var posts = _.filter(child.posts, function(post){
return post !== null && typeof post !== 'undefined';
});
if(!_.isEmpty(posts)){
_.forEach(posts, function(post){
post.isParent = false;
});
}
result = _.union(result, posts);
delete child.posts;
}
ownChildren = buildTree(data, child);
if(!_.isEmpty(ownChildren)){
child.isParent = true;
child.children = ownChildren;
}else{
child.isParent = false;
}
}
});
}
return result;
}
EDIT: made a new fiddle to contain the isParent part you can find it here
While this problem looks simple, I can remember to have struggled achieving it in a simple way. I therefore created a generic util to do so
You only have to write maximum 3 custom callbacks methods.
Here is an example:
import { flattenTreeItemDeep, treeItemFromList } from './tree.util';
import { sortBy } from 'lodash';
const listItems: Array<ListItem> = [
// ordered list arrival
{ id: 1, isFolder: true, parent: null },
{ id: 2, isFolder: true, parent: 1 },
{ id: 3, isFolder: false, parent: 2 },
// unordered arrival
{ id: 4, isFolder: false, parent: 5 },
{ id: 5, isFolder: true, parent: 1 },
// empty main level folder
{ id: 6, isFolder: true, parent: null },
// orphan main level file
{ id: 7, isFolder: false, parent: null },
];
const trees = treeItemFromList(
listItems,
(listItem) => listItem.isFolder, // return true if the listItem contains items
(parent, leafChildren) => parent.id === leafChildren.parent, // return true if the leaf children is contained in the parent
(parent, folderChildren) => parent.id === folderChildren.parent // return true if the children is contained in the parent
);
console.log(trees);
/*
[
{
children: [
{
children: [{ data: { id: 3, isFolder: false, parent: 2 }, isLeaf: true }],
data: { id: 2, isFolder: true, parent: 1 },
isLeaf: false,
},
{
children: [{ data: { id: 4, isFolder: false, parent: 5 }, isLeaf: true }],
data: { id: 5, isFolder: true, parent: 1 },
isLeaf: false,
},
],
data: { id: 1, isFolder: true, parent: null },
isLeaf: false,
},
{ children: [], data: { id: 6, isFolder: true, parent: null }, isLeaf: false },
{
data: {
id: 7,
isFolder: false,
parent: null,
},
isLeaf: true,
},
]
*/
I did not check with your example as all cases are different, you however need to implement only 3 methods to let the algorithm build the tree for you:
If the item is a folder or a leaf (in your case just check if the children contain any non falsy item) i.e. listItem.posts.some((value)=>!!value)
if a parent contains the leaf child, (parent, child) => !!parent.posts.filter((val)=>!!val).find(({id})=>child.id === id)
if a parent contains the folder: optional if this is the same logic as for a leaf child.

Categories

Resources