Javascript 递归过滤对象数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38132146/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 21:07:09  来源:igfitidea点击:

Recursively filter array of objects

javascriptarraysrecursionfilter

提问by Nathan Power

Hitting a wall with this one, thought I would post it here in case some kind soul has come across a similar one. I have some data that looks something like this:

用这个撞墙,我想我会把它贴在这里,以防有好心人遇到类似的。我有一些看起来像这样的数据:

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss2' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];

I don't know at run time how deep the hierarchy will be, i.e. how many levels of objects will have a children array. I have simplified the example somewhat, I will actually need to match the value properties against an array of search terms. Let's for the moment assume that I am matching where value.includes('Hit').

我不知道在运行时层次结构有多深,即有多少级别的对象将有一个子数组。我已经稍微简化了这个例子,我实际上需要将 value 属性与一组搜索词进行匹配。让我们暂时假设我匹配 where value.includes('Hit')

I need a function that returns a new array, such that:

我需要一个返回新数组的函数,例如:

  • Every non-matching object with no children, or no matches in children hierarchy, should not exist in output object

  • Every object with a descendant that contains a matching object, should remain

  • All descendants of matching objects should remain

  • 每个没有子级或子级层次结构中没有匹配项的非匹配对象不应存在于输出对象中

  • 每个具有包含匹配对象的后代的对象都应该保留

  • 匹配对象的所有后代都应该保留

I am considering a 'matching object' to be one with a valueproperty that contains the string Hitin this case, and vice versa.

在这种情况下,我正在考虑将“匹配对象”作为value包含字符串的属性的对象Hit,反之亦然。

The output should look something like the following:

输出应如下所示:

const expected = [
  {
    value: 'Miss1',
    children: [
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
    ]
  }
];

Many thanks to anyone who took the time to read this far, will post my solution if I get there first.

非常感谢花时间读到这里的任何人,如果我先到达那里,将发布我的解决方案。

回答by

Using .filter()and making a recursive call as I described in the comment above is basically what you need. You just need to update each .childrenproperty with the result of the recursive call before returning.

.filter()正如我在上面的评论中所描述的那样,使用和进行递归调用基本上是您所需要的。您只需要.children在返回之前使用递归调用的结果更新每个属性。

The return value is just the .lengthof the resulting .childrencollection, so if there's at least one, the object is kept.

返回值只是.length结果.children集合的 ,因此如果至少有一个,则保留该对象。

var res = input.filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.filter(f)).length
  }
})

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss2' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];

var res = input.filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.filter(f)).length
  }
})
console.log(JSON.stringify(res, null, 2))



Note that .includes()on a String is ES7, so may need to be patched for legacy browsers. You can use the traditional .indexOf("Hit") != -1in its place.

请注意,.includes()字符串是 ES7,因此可能需要针对旧版浏览器进行修补。您可以.indexOf("Hit") != -1在其位置使用传统。



To not mutate the original, create a map function that copies an object and use that before the filter.

为了不改变原始对象,创建一个映射函数来复制一个对象并在过滤器之前使用它。

function copy(o) {
  return Object.assign({}, o)
}

var res = input.map(copy).filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.map(copy).filter(f)).length
  }
})


To really squeeze the code down, you could do this:

要真正压缩代码,您可以这样做:

var res = input.filter(function f(o) {
  return o.value.includes("Hit") ||
         o.children && (o.children = o.children.filter(f)).length
})

Though it gets a little hard to read.

虽然读起来有点难。

回答by jj689

Here's a function that'll do what you're looking for. Essentially it will test every item in arrfor a match, then recursively call filter on its children. Also Object.assignis used so that the underlying object isn't changed.

这是一个可以完成您正在寻找的功能的功能。本质上,它将测试arr匹配中的每个项目,然后对其children. 也Object.assign被使用,以便不改变底层对象。

function filter(arr, term) {
    var matches = [];
    if (!Array.isArray(arr)) return matches;

    arr.forEach(function(i) {
        if (i.value.includes(term)) {
            matches.push(i);
        } else {
            let childResults = filter(i.children, term);
            if (childResults.length)
                matches.push(Object.assign({}, i, { children: childResults }));
        }
    })

    return matches;
}

回答by Zohaib Ijaz

I think it will be a recursive solution. Here is one that I tried.

我认为这将是一个递归解决方案。这是我尝试过的一种。

function find(obj, key) {
  if (obj.value && obj.value.indexOf(key) > -1){
    return true;
  }
  if (obj.children && obj.children.length > 0){
    return obj.children.reduce(function(obj1, obj2){
      return find(obj1, key) || find(obj2, key);
    }, {}); 
  } 
  return false;
}

var output = input.filter(function(obj){
     return find(obj, 'Hit');
 });
console.log('Result', output);

回答by Yuri Gor

Alternatively you can use _.filterDeepmethod from deepdashextension for lodash:

或者,您可以使用deepdash扩展中的_.filterDeep方法:lodash

var keyword = 'Hit';
var foundHit = _.filterDeep(
  input,
  function(value) {
    return value.value.includes(keyword);
  },
  {
    tree: true,
    onTrue: { skipChildren: true },
  }
);

Here is a full testfor your case

这是对您的案例的完整测试

回答by sudheer nunna

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss1' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14asds',
    children: [
      { value: 'Hit4sdas' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];

function filter(arr, term) {
    var matches = [];
    
    if (!Array.isArray(arr)) return matches;

    arr.forEach(function(i) {
     
        if (i.value === term) {
    
         const filterData =  (i.children && Array.isArray(i.children))? i.children.filter(values => values.value ===term):[];
         console.log(filterData)
         i.children =filterData;
            matches.push(i);
          
        } else {
       // console.log(i.children)
            let childResults = filter(i.children, term);
            if (childResults.length)
     matches.push(Object.assign({}, i, { children: childResults }));
        }
    })

    return matches;
}


const filterData= filter(input,'Miss1');
console.log(filterData)

Below code for filter the parent and child array data using recursive function

下面使用递归函数过滤父子数组数据的代码

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss2' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];

var res = input.filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.filter(f)).length
  }
})
console.log(JSON.stringify(res, null, 2))