【问题标题】:Filter a complicated data structure过滤复杂的数据结构
【发布时间】:2017-04-22 08:51:32
【问题描述】:

给定一个随机数据结构(可以是任何东西):

const data = [
 {
  name: "John",
  age: 26,
  company: {
   name: "Some company",
   address: "Some address"
  }
 },{...}
];

我希望能够在对象和子对象的所有值中进行搜索。例如,如果用户输入 John,我想返回所有包含“John”的对象,如果用户搜索“Some company”,我想也返回所有包含这些对象的对象。

我正在考虑扁平化每个对象的结构,然后过滤原始列表,但这不知何故感觉不对。有什么建议吗?

【问题讨论】:

  • 我会编写一个遍历所有对象键的递归函数。你试过什么了?你能分享你的代码吗?正如您作为经验丰富的用户所知,SO 不是代码编写服务 :-)
  • 这里有几种解决方案:stackoverflow.com/questions/39546088/…
  • @PatrickHund 我没有期待代码,我期待的是你刚才所做的建议;)

标签: javascript ecmascript-6


【解决方案1】:

您可以使用Object.values 递归搜索对象。

var data = [{ name: "John", age: 26, company: { name: "Some company", address: "Some address" } }, { name: "Jane", age: 32, company: { name: "Cameo", address: "2nd Root Dr" } }],
    find = 'Cameo',
    result = data.filter(o => Object.values(o).some(function search(v) {
        return v && typeof v === 'object' ? Object.values(v).some(search) : v === find;
    }));
  
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 哇。这确实是一个巧妙的解决方案。谢谢老哥!
【解决方案2】:

可以使用路径进行搜索,例如:

search(data, "name", "John"); // search for object with names equal to "John"
search(data, "company.adress", "some adress"); // search for objects with company adresses equal to "some adress"
// ...

代码如下:

function path2value(obj, path) {
    return path.split(".").reduce((o, p) => (o? o[p]: undefined), obj);
}

function search(arr, path, value) {
    return arr.filter(o => path2value(o, path) === value);
}

示例:

const data = [{
  name: "John",
  age: 26,
  company: {
    name: "Some company",
    address: "Some address"
  }
}, {
  name: "Ibrahim",
  age: 23,
  company: {
    name: "Some company",
    address: "Some address"
  }
}];

function path2value(obj, path) {
  return path.split(".").reduce((o, p) => (o ? o[p] : undefined), obj);
}

function search(arr, path, value) {
  return arr.filter(o => path2value(o, path) === value);
}

console.log(search(data, "name", "John"));
console.log(search(data, "company.name", "Some company"));

编辑:

如果您不想传入 path 并且想要递归地过滤对象。您可以创建一个函数来遍历对象及其子对象中的所有值并将它们传递给回调(您可以在其中指定是否包含该对象),如果回调返回 true,则将包含根对象在结果数组中,否则不会。该功能的使用如下:

filterRecursive(data, value => value === "John"); // filter the objects by value equals to "John"
filterRecursive(data, value => typeof(value) === "number" && value > 20); // filter the objects by values that are numbers and greater than 20
filterRecursive(data, value => /^Some/.test(value)); // filter the objects with values that start with "Some"

函数是:

function filterRecursive(arr, cb) {                                   // takes an array and a callback and recursively call the callback on each value in the object and sub object
    function hasIt(obj) {                                             // take an object and recurseively call the callback cb on its values and its subobjects values returning true if one of those values returned true, false if none of them returened true
        for(let key in obj) {                                         // for each key in the object
            if(obj.hasOwnProperty(key)) {                             // if the key is owned by this object
                if(Object.prototype.toString.call(obj[key]) === "[object Object]") {  // if the value on this key is another object...
                    if(hasIt(obj[key])) return true;                  // then call hasIt on it and if it returned true then return true and stop the search for this object
                }
                else if(cb(obj[key])) return true;                    // otherwise, if it's not an object, then pass it to the callback, if the callback returned true, then return true and stop the search
            }
        }
        return false;                                                 // return false if the recursive search failed
    }

    return arr.filter(o => hasIt(o)); // filter the root object by whether they have it or not (hasIt)
}

示例:

function filterRecursive(arr, cb) {
  function hasIt(obj) {
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (Object.prototype.toString.call(obj[key]) === "[object Object]") {
          if (hasIt(obj[key])) return true;
        } else if (cb(obj[key])) return true;
      }
    }
    return false;
  }

  return arr.filter(o => hasIt(o));
}

const data = [{
  name: "John",
  age: 26,
  company: {
    name: "Some company",
    address: "Some address"
  }
}, {
  name: "Ibrahim",
  age: 23,
  company: {
    name: "Some company",
    address: "Some address"
  }
}];

console.log(filterRecursive(data, v => v === "John"));
console.log(filterRecursive(data, v => /^Some/.test(v)));

【讨论】:

  • 这是一个简洁的解决方案。但是,关键是我不希望需要给出搜索位置的键。相反,我想搜索每个值:)
  • @Christian 查看上面的编辑:部分!
【解决方案3】:

你可以试试这样的:

function getObjectValues(obj, values){
  for(var key in obj){
    let value = obj[key];
    if(typeof value === "object"){
      getObjectValues(value, values);
    } else {
        values.push(value)
    }
  }
  return values;
}
    
const data = [ 
 {
  name: "John",
  age: 26,
  company: {
   name: "Some company",
   address: "Some address"
  }
},
{
  name: "Bar",
  age: 27,
  company: {
   name: "Some company",
   address: "Some address"
  }
},
{
  name: "Foo",
  age: 28,
  company: {
   name: "Some company John",
   address: "Some address"
  }
}];

let filteredData = data.filter(function(obj){
    return getObjectValues(obj,[]).some(function(value){
        return value.toString()
                    .includes("John");
    });
});


console.log(filteredData);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-21
    • 2014-09-29
    相关资源
    最近更新 更多