Nested arrays and targeting array by keys - javascript

I'm really not sure how to word this issue but I will try my best.
I have a nested array:
const items = [
{
id: 1,
name: "Test name",
children: [
{
id: 5,
name: "Test name 5",
children: [
{
id: 6,
name: "Test name 6",
children: [],
},
],
},
],
},
{
id: 8,
name: "Test name 8",
children: [
{
id: 9,
name: "Test name 9",
children: [],
},
],
},
];
and I have an array of indexes where to target and update
const keys = [0,0,0]
The array of indexes should target Test name 6
How can I update Test name 6 to something else?
p.s. items and keys are dynamic. there might be dozens of nested items or dozens of indexes in keys

You could reduce the indices and check the children property.
After getting the final object, just assign the value to the wanted property.
const
getItem = (children, keys) => keys.reduce(
({ children = [] }, i) => children[i],
{ children }
),
items = [{ id: 1, name: "Test name", children: [{ id: 5, name: "Test name 5", children: [{ id: 6, name: "Test name 6", children: [] }] }] }, { id: 8, name: "Test name 8", children: [{ id: 9, name: "Test name 9", children: [] }] }],
keys = [0, 0, 0];
console.log(getItem(items, keys));

To update your nested array first you will first need to change it to a mutable variable, i.e. from a const to a let or var.
From there you can update the value with something like:
// Copy pasted data from your post
let items = [
{
id: 1,
name: "Test name",
children: [
{
id: 5,
name: "Test name 5",
children: [
{
id: 6,
name: "Test name 6",
children: [],
},
],
},
],
},
{
id: 8,
name: "Test name 8",
children: [
{
id: 9,
name: "Test name 9",
children: [],
},
],
},
];
const keys = [0,0,0]
// <----------------- CODE BELOW ---------------->
// var to store item each time you dive deeper into the nested array
// initalize with the first item
let item = items[keys[0]].children;
// loop through each key
for(let i = 1; i < keys.length; i++){
// if we are at the last key, set item equal to the item object
if(i == keys.length-1){
item = item[keys[i]];
}
// otherwise, set item equal to the item's children
else{
item = item[keys[i]].children
}
}
// once item has been reached - all keys have been looped through - data can be minupulated
item.name = "new value"
// "Test name 6" has been updated to "new value"
emphasized text

In this scenario, I usually use a Map referencing the items within the nested arrays. For example:
const items = [
{
id: 1,
name: "Test name",
children: [
{
id: 5,
name: "Test name 5",
children: [
{
id: 6,
name: "Test name 6",
children: [],
},
],
},
],
},
{
id: 8,
name: "Test name 8",
children: [
{
id: 9,
name: "Test name 9",
children: [],
},
],
},
];
const createItemsMap = items => {
const m = new Map();
(function _collect(items) {
for (const item of items) {
m.set(item.id, item);
if (item.children) {
_collect(item.children);
}
}
})(items);
return m;
}
const itemsMap = createItemsMap(items);
// modify item #6
itemsMap.get(6).name = "Modified name 6";
console.log("itemsMap.get(1) === items[0] is", itemsMap.get(1) === items[0]);
console.log(items);
With that map, modifying the items is simply a matter of doing this :
const keys = [1,5,6];
for (const key of keys)
const item = itemsMap.get(key);
// update item
item.name = `Updated name ${key}`;
item.newProp = 'New value!';
}
// ex:
console.log(items[0]);
// {
// id: 1,
// name: "Updated name 1",
// newProp: "New value!',
// children: [
// {
// id: 5,
// name: "Updated name 5",
// newProps: "New value!",
// children: [
// {
// id: 6,
// name: "Updated name 6",
// newProps: "New value!",
// children: [],
// },
// ],
// },
// ],
// },
And a few more freebies :
// get all existing keys
const keys = Array.from(itemsMap.keys());
// check if an item exists
const exists3 = itemsMap.has(3); // false
const exists5 = itemsMap.has(5); // true
// adding new children
const newChild = { id: 3, name: "Test name 3", children: [] };
// ... add as child of 5
itemsMap.get(5).children.push(newChild);
// ... add it to the map
itemsMap.set(newChild.id, newChild); // that's it!
// remove an item
const removed = itemsMap.get(3);
// ... find if there is a parent...
const parent = Array.from(itemsMap.values()).find(item =>
item.children.includes(removed)
);
if (parent) {
// ... if there is a parent, remove the child
parent.children = parent.children.filter(item =>
item !== removed
);
} else {
// ... otherwise it is a root item, so remove it
items = items.filter(item => item !== removed);
}
// ... remove from map
itemsMap.delete(removed.id);
Note: If items is modified directly (i.e. items are added or removed), then itemsMap needs to either be re-generated, or preferably updated. In any other case, both items and itemsMap reference to the same data.

Related

How to create array of objects from 2 other array of objects with like values?

Here are 2 example arrays:
const inputData = [
{
ID: "2",
Title: "Updated",
ApprovedNamed: undefined,
},
{
ID: "3",
Title: "Update Title",
ApprovedNamed: undefined,
},
{
ID: "4",
Title: "Item Four",
ApprovedNamed: undefined,
},
];
const currentData = [
{
ID: "1",
Title: "Item One",
ApprovedNamed: undefined,
},
{
ID: "2",
Title: "Item Two",
ApprovedNamed: undefined,
},
{
ID: "3",
Title: "Item Three",
ApprovedNamed: undefined,
},
];
I am trying to build a new array of objects by comparing the inputData to currentData. If currentData contains an ID of any of inputData IDs then we make an array of const updateData and if it's new IDs (they aren't in currentData) then we make an array of const newData.
I tried this:
const updateData = inputData.map((row) => currentData.find((o) => o.ID=== row.ID));
but it fills updateData with everything regardless.
what should be happening based on the above data is:
updateData should have the objects for 2 and 3, while newData should have the object for ID 4 since it's not in currentData.
A way to do this:
// create a dictionary of inputs to leverage inbuild hashmap
let inputDic = inputData.reduce((dic,input) => {
dic[input.ID] = true;
return dic;
}, {});
let newArr = [], updatedArr = [];
// loop over all the existing assuming current will always have all existing inputData
currentData.forEach(current => {
if(inputDic[current.ID]){
updatedArr.push(current);
} else {
newArr.push(current);
}
});
console.log(newArr,updatedArr);

Selective deep clone [duplicate]

I have an array of deep JSON objects that look like similarly to this:
var hierarchy = [
{
"title": "category 1",
"children": [
{"title": "subcategory 1",
"children": [
{"id": 1, "title": "name 1"},
{"id": 2, "title": "name 2"},
{"id": 3, "title": "name 3"}
]
},
{"title": "subcategory 2",
"children": [
{"id": 1, "title": "name 4"}
]
}
]
},
{
"title": "category 2",
"children": [etc. - shortened for brevity]
}
];
So basically it is a hierarchy - there are categories which can have subcategories which contain objects with some IDs and names. I also have an array of IDs that are related to the deepest hierarchy level (objects with no children) and I need to filter this set of objects in such a way that only (sub)categories that contain defined objects remain.
So for example if I had an array containing two IDs:
var IDs = [2, 3];
the result would be:
var hierarchy = [
{
"title": "category 1",
"children": [
{"title": "subcategory 1",
"children": [
{"id": 2, "title": "name 2"},
{"id": 3, "title": "name 3"}
]
}
]
}
];
i.e. the whole, the whole 'category 2' object removed, the whole 'subcategory 2' removed, object with ID '1' removed.
The problem is that the depth of those objects is variable and unknown - some objects have no children, some have children that also have children etc., any subcategory can can itself have a subcategory and I basically need to find object with no children that have defined IDs and keep the whole path to each of them.
Thank you.
Basically, perform a depth first traversal of your tree invoking a callback function on each node. If that node is a leaf node and it's ID appears in your list then clone the branch that leads to that leaf, but don't re-clone any part of the branch that was already cloned.
Once you have constructed the partial and filtered copy of your tree you need to cleanup the original. I mutated the original tree in the process for book-keeping purposes - tracking which branches had already been cloned.
Edit: modified code to filter list of trees instead of just a single tree
var currentPath = [];
function depthFirstTraversal(o, fn) {
currentPath.push(o);
if(o.children) {
for(var i = 0, len = o.children.length; i < len; i++) {
depthFirstTraversal(o.children[i], fn);
}
}
fn.call(null, o, currentPath);
currentPath.pop();
}
function shallowCopy(o) {
var result = {};
for(var k in o) {
if(o.hasOwnProperty(k)) {
result[k] = o[k];
}
}
return result;
}
function copyNode(node) {
var n = shallowCopy(node);
if(n.children) { n.children = []; }
return n;
}
function filterTree(root, ids) {
root.copied = copyNode(root); // create a copy of root
var filteredResult = root.copied;
depthFirstTraversal(root, function(node, branch) {
// if this is a leaf node _and_ we are looking for its ID
if( !node.children && ids.indexOf(node.id) !== -1 ) {
// use the path that the depthFirstTraversal hands us that
// leads to this leaf. copy any part of this branch that
// hasn't been copied, at minimum that will be this leaf
for(var i = 0, len = branch.length; i < len; i++) {
if(branch[i].copied) { continue; } // already copied
branch[i].copied = copyNode(branch[i]);
// now attach the copy to the new 'parellel' tree we are building
branch[i-1].copied.children.push(branch[i].copied);
}
}
});
depthFirstTraversal(root, function(node, branch) {
delete node.copied; // cleanup the mutation of the original tree
});
return filteredResult;
}
function filterTreeList(list, ids) {
var filteredList = [];
for(var i = 0, len = list.length; i < len; i++) {
filteredList.push( filterTree(list[i], ids) );
}
return filteredList;
}
var hierarchy = [ /* your data here */ ];
var ids = [1,3];
var filtered = filterTreeList(hierarchy, ids);
You can use filterDeep method from deepdash extension for lodash:
var obj = [{/* get Vijay Jagdale's source object as example */}];
var idList = [2, 3];
var found = _.filterDeep(
obj,
function(value) {
return _.indexOf(idList, value.id) !== -1;
},
{ tree: true }
);
filtrate object will be:
[ { title: 'category 1',
children:
[ { title: 'subcategory 11',
children:
[ { id: 2, title: 'name 2' },
{ id: 3, title: 'name 3' } ] } ] },
{ title: 'category 2',
children:
[ { title: 'subcategory 21',
children: [ { id: 3, title: 'name cat2sub1id3' } ] } ] } ]
Here is the full working test for your use case
Although this is an old question I will add my 2 cents. The solution requires a straightforward iteration through the loops, subloops etc. and then compare IDs and build the resultant object. I have pure-javascript and jQuery solution. While the pure javascript works for the example above, I would recommend the jQuery solution, because it is more generic, and does a "deep copy" of the objects, in case you have large and complex objects you won't run into bugs.
function jsFilter(idList){
var rsltHierarchy=[];
for (var i=0;i<hierarchy.length;i++) {
var currCatg=hierarchy[i];
var filtCatg={"title":currCatg.title, "children":[]};
for (var j=0;j<currCatg.children.length;j++) {
var currSub=currCatg.children[j];
var filtSub={"title":currSub.title, "children":[]}
for(var k=0; k<currSub.children.length;k++){
if(idList.indexOf(currSub.children[k].id)!==-1)
filtSub.children.push({"id":currSub.children[k].id, "title":currSub.children[k].title});
}
if(filtSub.children.length>0)
filtCatg.children.push(filtSub);
}
if(filtCatg.children.length>0)
rsltHierarchy.push(filtCatg);
}
return rsltHierarchy;
}
function jqFilter(idList){
var rsltHierarchy=[];
$.each(hierarchy, function(index,currCatg){
var filtCatg=$.extend(true, {}, currCatg);
filtCatg.children=[];
$.each(currCatg.children, function(index,currSub){
var filtSub=$.extend(true, {}, currSub);
filtSub.children=[];
$.each(currSub.children, function(index,currSubChild){
if(idList.indexOf(currSubChild.id)!==-1)
filtSub.children.push($.extend(true, {}, currSubChild));
});
if(filtSub.children.length>0)
filtCatg.children.push(filtSub);
});
if(filtCatg.children.length>0)
rsltHierarchy.push(filtCatg);
});
return rsltHierarchy;
}
//Now test the functions...
var hierarchy = eval("("+document.getElementById("inp").value+")");
var IDs = eval("("+document.getElementById("txtBoxIds").value+")");
document.getElementById("oupJS").value=JSON.stringify(jsFilter(IDs));
$(function() {
$("#oupJQ").text(JSON.stringify(jqFilter(IDs)));
});
#inp,#oupJS,#oupJQ {width:400px;height:100px;display:block;clear:all}
#inp{height:200px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
ID List: <Input id="txtBoxIds" type="text" value="[2, 3]">
<p>Input:
<textarea id="inp">[
{
"title": "category 1",
"children": [
{"title": "subcategory 11",
"children": [
{"id": 1, "title": "name 1"},
{"id": 2, "title": "name 2"},
{"id": 3, "title": "name 3"}
]
},
{"title": "subcategory 12",
"children": [
{"id": 1, "title": "name 4"}
]
}
]
},
{
"title": "category 2",
"children": [
{"title": "subcategory 21",
"children": [
{"id": 3, "title": "name cat2sub1id3"},
{"id": 5, "title": "name cat2sub1id5"}
]
},
{"title": "subcategory 22",
"children": [
{"id": 6, "title": "name cat2sub2id6"},
{"id": 7, "title": "name cat2sub2id7"}
]
}
]
}
]</textarea>
<p>Pure-Javascript solution results:
<textarea id="oupJS"></textarea>
<p>jQuery solution results:
<textarea id="oupJQ"></textarea>
I'd not reinvent the wheel. We use object-scan for most of our data processing now and it solves your question nicely. Here is how
// const objectScan = require('object-scan');
const filter = (input, ids) => {
objectScan(['**[*]'], {
filterFn: ({ value, parent, property }) => {
if (
('id' in value && !ids.includes(value.id))
|| ('children' in value && value.children.length === 0)
) {
parent.splice(property, 1);
}
}
})(input);
};
const hierarchy = [ { title: 'category 1', children: [ { title: 'subcategory 1', children: [ { id: 1, title: 'name 1' }, { id: 2, title: 'name 2' }, { id: 3, title: 'name 3' } ] }, { title: 'subcategory 2', children: [ { id: 1, title: 'name 4' } ] } ] }, { title: 'category 2', children: [] } ];
filter(hierarchy, [2, 3]);
console.log(hierarchy);
// => [ { title: 'category 1', children: [ { title: 'subcategory 1', children: [ { id: 2, title: 'name 2' }, { id: 3, title: 'name 3' } ] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Add child or remove item by path from tree like array of objects

I have nested array of objects like
let treeArr =
{
name: "Top Level", id:'a12',
children: [
{
name: "Level 2: A", id:'a',
children: [
{ name: "Daughter of A", id: 'a',
children: [
{ name: "Another Sub0 Issuer", id: '504' },
{ name: "Another Sub1 Issuer", id: '109' },
{ name: "Another Sub2 Issuer", id: '209' },
]
},
{ name: "Daughter of A", id: '165' },
]
},
{
name: "ABC Co LLC", id:'1234',
children: [
{ name: "Daughter of A", id: 'a' },
{ name: "Daughter of A", id: 'x' },
{ name: "Daughter of Y", id: 'a',
children:[
{ name: "Another Suba Issuer", id: '219' },
{ name: "Another Subb Issuer", id: '409',
children:[
{ name: "Another 4th Issuer", id: '200' },
{ name: "Another 4th Issuer", id: '300' },
{ name: "Another 4th Issuer", id: '400' },
]
},
{ name: "Another Suba Issuer", id: '479' },
]
}
]
}
]
}
function findIndexNested(data, id) {
if (data.id === id) return [];
let result;
const i = (data.children || []).findIndex(child => {
return result = findIndexNested(child, id)
});
if (result) return [i, ...result];
}
function findByPath(data, path) {
for (let i of path) data = data.children[i];
return data
}
I need to delete or add children to certain children items to/from treeArr,
my finder method returns index of searched item, for example: [0, 2, 0, 1]
first children of third children of first ...
so i need to generate this as code dynamically instead hardcoded
my current ugly solution is looks like below, path returns from findIndexNested(.,.)
treeArr.children[path[0]].children[path[1]].children[path[2]].children[path[3]]
how can I add child or remove found item from treeArr ?
function removebyPath(obj, path, i, len ) {
if(len===i+1){ delete obj.children[path[i]]; return;}
removebyPath(obj.children[path[i]],path, i+1, len );
}
let path = findIndexNested(obj, '219' );
removebyPath(obj, path, 0, path.length);
console.log('result:', obj );
removebyPath method removes item from passed path

filter is not working for third child level (nested level)

I'm trying to filter an array having nested level of 3.. I have to filter this array at the last level.
array = [{
children: [{
children: [{
children: [],
id: 2694,
name: "Some Random data"
}, {
children: [],
id: 2695,
name: "Another Random Data"
}],
id: 2574,
name: "Test data",
}],
id: 2530,
name: "Main Test data"
}, {
children: [{
children: [{
children: [],
id: 2696,
name: "Secondary test Data"
}, {
children: [],
id: -1,
name: "Random Text"
}],
id: 2575,
name: "Another random Text"
}],
id: 2531,
name: "New Data"
}]
I have tried this function
function(random){
let array3=[];
this.array.forEach(cap=>{
let tempparent={...cap};
let child1= tempparent.children.forEach(ch=>{
let tempfeat={...ch};
let tempchildren = tempfeat.children.filter(fe=>{
if(fe.id!=random.id){
return fe
}
});
// console.log(tempchildren)
tempfeat.children = tempchildren;
// console.log(tempfeat.children)
});
console.log(child1)
tempparent.children= child1;
console.log(tempparent.children)
nodes3.push(tempparent)
})
this.array= array3
console.log(this.array);
}
I want to filter it at third level using id value. when the id matches the matched object must be removed.
You could take a dynamic approach and take the children out of the object, check the id and if found, ignore the object.
Otherwise take the children and get a subset with a recursive call and rebuild a new object and push this one to the result set.
This approach does not mutate the original data, but returns all new objects and works for an arbitrary count of levels.
function remove(array, id) {
return array.reduce((r, { children, ...o }) => {
if (o.id === id) return r;
children = remove(children || [], id);
if (children.length) o.children = children;
r.push(o);
return r;
}, []);
}
var data = [{ children: [{ children: [{ children: [], id: 2694, name: "Some Random data" }, { children: [], id: 2695, name: "Another Random Data" }], id: 2574, name: "Test data", }], id: 2530, name: "Main Test data" }, { children: [{ children: [{ children: [], id: 2696, name: "Secondary test Data" }, { children: [], id: -1, name: "Random Text" }], id: 2575, name: "Another random Text" }], id: 2531, name: "New Data" }],
result = remove(data, 2574);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use recursive function.
For example, like this
function removeById(_id,arr){
if(arr.id === _id){
return true;
}else{
arr.children.forEach(currentItem => {
if(getById(_id,currentItem)){
arr.children = arr.children.filter(x=>x.id !== _id);
}
});
}
}
and using this function
removeById(2694,array[0]);
removeById(2694,array[1]);
please examine example

JavaScript: how to filter deep JSON objects

I have an array of deep JSON objects that look like similarly to this:
var hierarchy = [
{
"title": "category 1",
"children": [
{"title": "subcategory 1",
"children": [
{"id": 1, "title": "name 1"},
{"id": 2, "title": "name 2"},
{"id": 3, "title": "name 3"}
]
},
{"title": "subcategory 2",
"children": [
{"id": 1, "title": "name 4"}
]
}
]
},
{
"title": "category 2",
"children": [etc. - shortened for brevity]
}
];
So basically it is a hierarchy - there are categories which can have subcategories which contain objects with some IDs and names. I also have an array of IDs that are related to the deepest hierarchy level (objects with no children) and I need to filter this set of objects in such a way that only (sub)categories that contain defined objects remain.
So for example if I had an array containing two IDs:
var IDs = [2, 3];
the result would be:
var hierarchy = [
{
"title": "category 1",
"children": [
{"title": "subcategory 1",
"children": [
{"id": 2, "title": "name 2"},
{"id": 3, "title": "name 3"}
]
}
]
}
];
i.e. the whole, the whole 'category 2' object removed, the whole 'subcategory 2' removed, object with ID '1' removed.
The problem is that the depth of those objects is variable and unknown - some objects have no children, some have children that also have children etc., any subcategory can can itself have a subcategory and I basically need to find object with no children that have defined IDs and keep the whole path to each of them.
Thank you.
Basically, perform a depth first traversal of your tree invoking a callback function on each node. If that node is a leaf node and it's ID appears in your list then clone the branch that leads to that leaf, but don't re-clone any part of the branch that was already cloned.
Once you have constructed the partial and filtered copy of your tree you need to cleanup the original. I mutated the original tree in the process for book-keeping purposes - tracking which branches had already been cloned.
Edit: modified code to filter list of trees instead of just a single tree
var currentPath = [];
function depthFirstTraversal(o, fn) {
currentPath.push(o);
if(o.children) {
for(var i = 0, len = o.children.length; i < len; i++) {
depthFirstTraversal(o.children[i], fn);
}
}
fn.call(null, o, currentPath);
currentPath.pop();
}
function shallowCopy(o) {
var result = {};
for(var k in o) {
if(o.hasOwnProperty(k)) {
result[k] = o[k];
}
}
return result;
}
function copyNode(node) {
var n = shallowCopy(node);
if(n.children) { n.children = []; }
return n;
}
function filterTree(root, ids) {
root.copied = copyNode(root); // create a copy of root
var filteredResult = root.copied;
depthFirstTraversal(root, function(node, branch) {
// if this is a leaf node _and_ we are looking for its ID
if( !node.children && ids.indexOf(node.id) !== -1 ) {
// use the path that the depthFirstTraversal hands us that
// leads to this leaf. copy any part of this branch that
// hasn't been copied, at minimum that will be this leaf
for(var i = 0, len = branch.length; i < len; i++) {
if(branch[i].copied) { continue; } // already copied
branch[i].copied = copyNode(branch[i]);
// now attach the copy to the new 'parellel' tree we are building
branch[i-1].copied.children.push(branch[i].copied);
}
}
});
depthFirstTraversal(root, function(node, branch) {
delete node.copied; // cleanup the mutation of the original tree
});
return filteredResult;
}
function filterTreeList(list, ids) {
var filteredList = [];
for(var i = 0, len = list.length; i < len; i++) {
filteredList.push( filterTree(list[i], ids) );
}
return filteredList;
}
var hierarchy = [ /* your data here */ ];
var ids = [1,3];
var filtered = filterTreeList(hierarchy, ids);
You can use filterDeep method from deepdash extension for lodash:
var obj = [{/* get Vijay Jagdale's source object as example */}];
var idList = [2, 3];
var found = _.filterDeep(
obj,
function(value) {
return _.indexOf(idList, value.id) !== -1;
},
{ tree: true }
);
filtrate object will be:
[ { title: 'category 1',
children:
[ { title: 'subcategory 11',
children:
[ { id: 2, title: 'name 2' },
{ id: 3, title: 'name 3' } ] } ] },
{ title: 'category 2',
children:
[ { title: 'subcategory 21',
children: [ { id: 3, title: 'name cat2sub1id3' } ] } ] } ]
Here is the full working test for your use case
Although this is an old question I will add my 2 cents. The solution requires a straightforward iteration through the loops, subloops etc. and then compare IDs and build the resultant object. I have pure-javascript and jQuery solution. While the pure javascript works for the example above, I would recommend the jQuery solution, because it is more generic, and does a "deep copy" of the objects, in case you have large and complex objects you won't run into bugs.
function jsFilter(idList){
var rsltHierarchy=[];
for (var i=0;i<hierarchy.length;i++) {
var currCatg=hierarchy[i];
var filtCatg={"title":currCatg.title, "children":[]};
for (var j=0;j<currCatg.children.length;j++) {
var currSub=currCatg.children[j];
var filtSub={"title":currSub.title, "children":[]}
for(var k=0; k<currSub.children.length;k++){
if(idList.indexOf(currSub.children[k].id)!==-1)
filtSub.children.push({"id":currSub.children[k].id, "title":currSub.children[k].title});
}
if(filtSub.children.length>0)
filtCatg.children.push(filtSub);
}
if(filtCatg.children.length>0)
rsltHierarchy.push(filtCatg);
}
return rsltHierarchy;
}
function jqFilter(idList){
var rsltHierarchy=[];
$.each(hierarchy, function(index,currCatg){
var filtCatg=$.extend(true, {}, currCatg);
filtCatg.children=[];
$.each(currCatg.children, function(index,currSub){
var filtSub=$.extend(true, {}, currSub);
filtSub.children=[];
$.each(currSub.children, function(index,currSubChild){
if(idList.indexOf(currSubChild.id)!==-1)
filtSub.children.push($.extend(true, {}, currSubChild));
});
if(filtSub.children.length>0)
filtCatg.children.push(filtSub);
});
if(filtCatg.children.length>0)
rsltHierarchy.push(filtCatg);
});
return rsltHierarchy;
}
//Now test the functions...
var hierarchy = eval("("+document.getElementById("inp").value+")");
var IDs = eval("("+document.getElementById("txtBoxIds").value+")");
document.getElementById("oupJS").value=JSON.stringify(jsFilter(IDs));
$(function() {
$("#oupJQ").text(JSON.stringify(jqFilter(IDs)));
});
#inp,#oupJS,#oupJQ {width:400px;height:100px;display:block;clear:all}
#inp{height:200px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
ID List: <Input id="txtBoxIds" type="text" value="[2, 3]">
<p>Input:
<textarea id="inp">[
{
"title": "category 1",
"children": [
{"title": "subcategory 11",
"children": [
{"id": 1, "title": "name 1"},
{"id": 2, "title": "name 2"},
{"id": 3, "title": "name 3"}
]
},
{"title": "subcategory 12",
"children": [
{"id": 1, "title": "name 4"}
]
}
]
},
{
"title": "category 2",
"children": [
{"title": "subcategory 21",
"children": [
{"id": 3, "title": "name cat2sub1id3"},
{"id": 5, "title": "name cat2sub1id5"}
]
},
{"title": "subcategory 22",
"children": [
{"id": 6, "title": "name cat2sub2id6"},
{"id": 7, "title": "name cat2sub2id7"}
]
}
]
}
]</textarea>
<p>Pure-Javascript solution results:
<textarea id="oupJS"></textarea>
<p>jQuery solution results:
<textarea id="oupJQ"></textarea>
I'd not reinvent the wheel. We use object-scan for most of our data processing now and it solves your question nicely. Here is how
// const objectScan = require('object-scan');
const filter = (input, ids) => {
objectScan(['**[*]'], {
filterFn: ({ value, parent, property }) => {
if (
('id' in value && !ids.includes(value.id))
|| ('children' in value && value.children.length === 0)
) {
parent.splice(property, 1);
}
}
})(input);
};
const hierarchy = [ { title: 'category 1', children: [ { title: 'subcategory 1', children: [ { id: 1, title: 'name 1' }, { id: 2, title: 'name 2' }, { id: 3, title: 'name 3' } ] }, { title: 'subcategory 2', children: [ { id: 1, title: 'name 4' } ] } ] }, { title: 'category 2', children: [] } ];
filter(hierarchy, [2, 3]);
console.log(hierarchy);
// => [ { title: 'category 1', children: [ { title: 'subcategory 1', children: [ { id: 2, title: 'name 2' }, { id: 3, title: 'name 3' } ] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Categories