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
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)
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);
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);
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, ''));
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.