简体   繁体   English

JavaScript:如何过滤深层 JSON 对象

[英]JavaScript: how to filter deep JSON objects

I have an array of deep JSON objects that look like similarly to this:我有一组与此类似的深度 JSON 对象:

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.所以基本上它是一个层次结构 - 有些类别可以有子类别,其中包含具有一些 ID 和名称的对象。 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.我还有一个与最深层次级别(没有子对象的对象)相关的 ID 数组,我需要以这样一种方式过滤这组对象,只保留包含定义对象的(子)类别。

So for example if I had an array containing two IDs:例如,如果我有一个包含两个 ID 的数组:

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"}
        ]
      }
    ]
  }
];

ie the whole, the whole 'category 2' object removed, the whole 'subcategory 2' removed, object with ID '1' removed.即整个,删除整个“类别 2”对象,删除整个“子类别 2”,删除 ID 为“1”的对象。

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.问题是这些对象的深度是可变的和未知的 - 有些对象没有孩子,有些有孩子也有孩子等等,任何子类别本身都可以有一个子类别,我基本上需要找到没有孩子的对象定义的 ID 并保留每个 ID 的完整路径。

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.如果该节点是叶节点并且它的 ID 出现在您的列表中,则克隆通向该叶节点的分支,但不要重新克隆已经克隆的分支的任何部分。

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 :您可以使用来自deepdash扩展的filterDeep方法为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: filtrate对象将是:

[ { 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.虽然这是一个老问题,但我会加上我的 2 美分。 The solution requires a straightforward iteration through the loops, subloops etc. and then compare IDs and build the resultant object.该解决方案需要通过循环、子循环等直接迭代,然后比较 ID 并构建结果对象。 I have pure-javascript and jQuery solution.我有纯 javascript 和 jQuery 解决方案。 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.虽然纯 javascript 适用于上面的示例,但我会推荐 jQuery 解决方案,因为它更通用,并且可以对对象进行“深度复制”,以防您拥有大型和复杂的对象,您不会遇到错误。

 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免责声明:我是对象扫描的作者

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM