javascriptjqueryarrayslinq.js

Array of objects: return only objects (and children) where attribute matches


I have an array with several "levels" or children. I would like to create a new array of objects where archived===false so that no archived objects are listed.

So this...

var arr = [
    {"id":1, "archived":false,"children":[
        {"id":1,"archived":true, "subs":[
            {"id":1,"archived":true},
            {"id":2,"archived":true},
            {"id":3,"archived":true}
        ]},
        {"id":2,"archived":false, "subs":[
            {"id":1,"archived":false},
            {"id":2,"archived":false},
            {"id":3,"archived":true}
        ]}
    ]},
    {"id":2, "archived":true,"children":[
        {"id":1,"archived":true, "subs":[
            {"id":1,"archived":true},
            {"id":2,"archived":true},
            {"id":3,"archived":true}
        ]},
        {"id":2,"archived":true, "subs":[
            {"id":1,"archived":true},
            {"id":2,"archived":true},
            {"id":3,"archived":true}
        ]}
    ]},
    {"id":3, "archived":false,"children":[
        {"id":1,"archived":false, "subs":[
            {"id":1,"archived":true},
            {"id":2,"archived":false},
            {"id":3,"archived":true}
        ]},
        {"id":2,"archived":false, "subs":[
            {"id":1,"archived":false},
            {"id":2,"archived":false},
            {"id":3,"archived":false}
        ]}
    ]}
];

Becomes...

var arr = [
    {"id":1, "archived":false,"children":[
        {"id":2,"archived":false, "subs":[
            {"id":1,"archived":false},
            {"id":2,"archived":false}
        ]}
    ]},
    {"id":3, "archived":false,"children":[
        {"id":1,"archived":false, "subs":[
            {"id":2,"archived":false}
        ]},
        {"id":2,"archived":false, "subs":[
            {"id":1,"archived":false},
            {"id":2,"archived":false},
            {"id":3,"archived":false}
        ]}
    ]}
];

I've tried combinations of map, reduce, filter, and grep...but I probably do not have the order right, etc. I know I could just loop through each level, but I can't get it just right.

It would be great to have this contained in a linq.js enumerable array or in jQuery.

Non-working Example fiddle


Solution

  • A well crafted function to handle recursion could make doing this with linq.js fairly simple to do. This in particular doesn't necessarily need linq.js, but it keeps it simple.

    function isNotArchived(childrenName, childrenFilter) {
      return function (e) {
        var filtered = e.Where("!$.archived");
        if (childrenName && childrenFilter) {
          return filtered.Select(function (c) {
            var result = { id: c.id, archived: c.archived };
            result[childrenName] = $.Enumerable.From(c[childrenName])
              .Let(childrenFilter)
              .ToArray();
            return result;
          });
        }
        return filtered;
      };
    }
    var filtered = $.Enumerable.From(arr)
      .Let(isNotArchived("children",
        isNotArchived("subs",
          isNotArchived()
        )
      ))
      .ToArray();
    

    fiddle