如何在包含嵌套对象的对象数组中搜索值

时间:2018-10-30 07:17:51

标签: javascript angularjs lodash

我想开发此功能来搜索/过滤列表。基本上,我将从输入框中获取搜索词,然后必须从数组中获取所有包含搜索词的项。

这是到目前为止我尝试过的方法,它适用于根级属性,但不适用于嵌套数组/对象:

// Filter List
this.filterList = query => {
  if (typeof query === "string") {
    // transform query to lowercase
    query = query.toLowerCase();
    // clear the current list being displayed
    this.filteredList = [];
    // filter the lsit and store the results with
    // matching criteria in "filteredList" array
    let filteredResults = _.filter(this.itemList, item => {
      if (item && typeof item === "object") {
        // loop over the item object
        for (let property in item) {
          if (item.hasOwnProperty(property)) {
            let key = item[property];
            // address, phone and emails
            if (typeof key === "object" && _.isArray(key)) {
              _.filter(key, element => {
                if (typeof element === "object") {
                  for (let nestedProperty in element) {
                    let nestedKey = element[nestedProperty];
                    if (nestedKey) {
                      nestedKey = nestedKey.toString().toLowerCase();
                    }
                    if (nestedKey && nestedKey.includes(query)) {
                      return item;
                    }
                  }
                }
              });
            } else {
              if (key) key = key.toString().toLowerCase();
              if (key && key.includes(query)) return item;
            }
          }
        }
      }
    });
    // assign the filtered results to the list being displayed
    this.filteredList = [...filteredResults];
  } else {
    // if query is empty or null or anything other than string
    // revert all changes and assign the original list to display list
    this.filteredList = this.itemList;
  }
};

如果有帮助,这是我要遍历的数组中的一个对象:

[
  {
    "id": "number",
    "dealerCode": "string",
    "name": "string",
    "gstin": "string",
    "pan": "string",
    "cin": "string",
    "emails": [
      { 
        "name": "string", 
        "address": "string", 
        "isPrimary": "boolean"
      }
    ],
    "phoneNumbers": [
      { 
        "countryCode": "number", 
        "number": "number", 
        "isPrimary": "boolean"
      }
    ],
    "addresses": [
      {
        "addressLine1": "string",
        "addressLine2": "string",
        "addressLine3": "string",
        "country": "string",
        "state": "string",
        "city": "string",
        "postalCode": "number",
        "isPrimary": "boolean"
      }
    ],
    "status": "string",
    "statusId": "number"
  }
]

我正在AngularJS中进行此操作,也使用了Lodash。

2 个答案:

答案 0 :(得分:1)

对于类似这样的问题,您需要比较不同类型的基元和对象/数组,通常最好使用递归命名函数。根据以下假设,这可能应该可以解决您正在寻找的问题:

  1. 用户将所有条目视为字符串。因此99和“ 99”相同。我将在进行此假设的代码中添加注释
  2. 所有条目都不区分大小写(均转换为小写)
  3. 没有设置嵌套对象/数组的深度;以下解决方案可对异构列表的任何深度进行递归工作
  4. 如果任何叶子节点中的任何内容匹配,则将返回整个对象

该解决方案的工作方式如下:

  • 通过顶层列表进行过滤,并与userEntry相比,在每个dataItem上调用matchEntryInTree
  • matchesEntryInTree将检查每个dataItem并查看它是数组还是对象
    • 如果dataItem是数组/对象,我们可以通过递归调用matchEntryInTree再次深入研究它们
    • 如果不是,我们调用compareValues来查看条目是否与当前dataItem匹配
  • 使用上面的递归模式,所有叶节点(无论树的形状如何)都将与初始userEntry比较

// test data for trial runs
const testData = [
  {
    id: 123488,
    dealerCode: "ACb3",
    name: "Some Name",
    gstin: "string",
    pan: "string",
    cin: "string",
    emails: [
      {
        name: "Some Email name",
        address: "anemail.domain.com",
        isPrimary: "boolean"
      }
    ],
    phoneNumbers: [
      {
        countryCode: "9398",
        number: "number",
        isPrimary: "boolean"
      }
    ],
    addresses: [
      {
        addressLine1: "Florida",
        addressLine2: "Street place",
        addressLine3: "string",
        country: "string",
        state: "string",
        city: "string",
        postalCode: "number",
        isPrimary: "boolean"
      }
    ],
    status: "string",
    statusId: "number"
  },
  {
    id: 88888,
    dealerCode: "NMC",
    name: "Some Other",
    gstin: "string",
    pan: "string",
    cin: "string",
    emails: [
      {
        name: "An Email thing",
        address: "athing.somewhere.org",
        isPrimary: "boolean"
      }
    ],
    phoneNumbers: [
      {
        countryCode: "93948",
        number: "number",
        isPrimary: "boolean"
      }
    ],
    addresses: [
      {
        addressLine1: "Denver",
        addressLine2: "Street place",
        addressLine3: "string",
        country: "string",
        state: "string",
        city: "string",
        postalCode: "number",
        isPrimary: "boolean"
      }
    ],
    status: "string",
    statusId: "number"
  }
];


// broke these into separate helper functions, but you can combine all of them except the recursive one if you'd like
const returnFilterResults = (userEntry, dataItems) => {
  const compareValues = (entry, dataValue) => {
    if ( _.isBoolean(dataValue)) {
      return entry === dataValue;
    } else if (_.isNumber(dataValue)) {
    // if the dataValue is a number, we convert both it and the user's entry (which probably already is a string) to a string to compare
    // you can make this comparison more strict if desired
      return _.includes(_.toLower(_.toString(dataValue)), _.toLower(entry));
    } else if (_.isString(dataValue)) {
      return _.includes(_.toLower(dataValue), _.toLower(entry));
    } else {
      return false;
    }
  };

  const matchesEntryInTree = (entry, dataItem) => {
  // if this dataItem is an object or array, let's drill back in again
    if (_.isObject(dataItem) || _.isArray(dataItem)) {
      // as we recursively move through the tree, check to see if *any* of the entries match, using 'some'
      return _.some(dataItem, innerDataItem => {
        return matchesEntryInTree(entry, innerDataItem);
      });
    } else {
    // if it's a primitive, then let's compare directly
      return compareValues(entry, dataItem);
    }
  };

  // I created a results variable so we could console log here in this snippet
  // but you can just return from the filter directly
  const results = _.filter(dataItems, dataItem => {
    return matchesEntryInTree(userEntry, dataItem);
  });

  console.log(userEntry, results);
  return results;
};

returnFilterResults("place", testData);
// both entries return

returnFilterResults("Denver", testData);
// second entry is returned

returnFilterResults(48, testData);
// both entries return - ID in first, countryCode in second

returnFilterResults(12, testData);
// first entry is returned
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

答案 1 :(得分:0)

您为什么不使用Flatten函数来展平对象/ JSON然后搜索您的值?以下是一个示例:

var flattenObject = function(ob) {
    var toReturn = {};

    for (var i in ob) {
        if (!ob.hasOwnProperty(i)) continue;

        if ((typeof ob[i]) == 'object') {
            var flatObject = flattenObject(ob[i]);
            for (var x in flatObject) {
                if (!flatObject.hasOwnProperty(x)) continue;

                toReturn[i + '.' + x] = flatObject[x];
            }
        } else {
            toReturn[i] = ob[i];
        }
    }
    return toReturn;
};

对于嵌套对象,假设

{
  A : {
    B: {
     C: "V"
    }
  }
}

您将获得一个键为A.B.C且值为“ V”的对象。这样,您只有一个级别可以搜索您的价值。

希望这会有所帮助! 干杯!