【问题标题】:Find object from an array of nested objects by key in JavaScript在 JavaScript 中按键从嵌套对象数组中查找对象
【发布时间】:2021-10-04 03:15:07
【问题描述】:

我有一个像这样的 JSON 嵌套对象树。 如何通过将 ID 值传递给函数来获取指定的对象。 (ID 是所有嵌套对象中的键)

我尝试了递归和 JSON.stringify().findObject() 但不知何故它们不起作用。

{
    "id": "A",
    "name": "Item A",
    "child": [
        {
            "id": "B",
            "name": "Item B",
            "child": [
                {
                    "id": "C",
                    "name": "Item C"
                    "child": []
                }
            ]
        },
        {
            "id": "D",
            "name": "Item D",
            "child": []
        }
    ]
}


//Calling like this
var result = findObject("C");

那么console.log(result);应该是

{
  "id": "C",
  "name": "Item C"
  "child": []
}

【问题讨论】:

    标签: javascript object recursion


    【解决方案1】:

    Nick Parsons 的回答很棒,代码写得很好,解释也很好。

    但我认为有可能把它写得更通用更简单。

    虽然这里的要求是通过id 进行搜索,但我们可以很容易地想象出我们想要匹配的各种其他方式。我们可以编写一个带有任意谓词的通用版本,然后用一个简单的函数对其进行配置,而不是试图预测所有这些。

    这是一种方法:

    const deepFind = (pred) => ([x, ...xs] = []) => 
      x && (pred (x) ? x : deepFind (pred) (x.child) || deepFind (pred) (xs))
    
    const findById = (id) => (obj) =>  
      deepFind ((o) => o.id == id) ([obj])
    
    const input = {id: "A", name: "Item A", child: [{id: "B", name: "Item B", child: [{id: "C", name: "Item C", child: []}]}, {id: "D", name: "Item D", child: []}]};
    
    console .log ('C:', findById ('C') (input)) //~> {id: "C", name: "Item C", child: []}
    console .log ('X:', findById ('X') (input)) //~> undefined

    deepFind 接受一个谓词函数并返回一个函数,该函数接受一个数组,并在深度优先搜索(在node -> node.child 树上)与谓词匹配时返回第一个匹配项。

    我们用 findById 包装它,它接受一个目标 id,并返回一个函数,该函数接受一个输入对象,将该对象包装在一个数组中,并使用谓词调用 deepFind 以测试 id 是否与目标匹配和那个数组。

    通过向 deepFind 添加另一个初始参数来告诉我们树的结构,我们可以轻松地使其更通用。 (node -> node.children 可能更常见。)但这是另一天的任务。

    deepFind 可能有一个稍微密集的实现。它可以用多种方式编写,如果其中一种对您更有意义:

    const deepFind = (pred) => (xs) => {
      for (let x of xs) {
        if (pred (x)) {return x}
        return deepFind (pred) (x .child || [])
      }
    }
    

    const deepFind = (pred) => ([x, ...xs]) => 
      x == undefined
        ? undefined
        : pred (x) 
          ? x
          : deepFind (pred) (x.child || []) || deepFind (pred) (xs)
    

    或在traverse 生成器函数之上:

    function * traverse (xs = []) {
      for (let x of xs) {
        yield x;
        yield * traverse (x.child || [])
      }
    }
    
    const deepFind = (pred) => (obj) => {
      for (let node of traverse ([obj])) {
        if (pred (node)) {return node} 
      }
    }
    

    我们还可以找到更多。

    【讨论】:

      【解决方案2】:

      您可以使用 findObject 将您的父 obj 包装在一个数组中,然后调用一个辅助函数 findObjectAux,该函数负责对您的数组执行迭代以找到您的匹配项(请注意,对于父对象,您的数组中只有一项,但后续调用可能包含更多)。对于数组中的每个对象,您可以检查它的 id 是否与您传递给函数的那个​​相匹配。如果匹配,您可以将其返回给调用函数,如果不匹配,则可以递归其child 数组。如果查看 objects 子数组恰好返回一个值,那么您已经找到了可以返回的匹配项,否则,您的 for 循环可以继续查看数组中的任何剩余对象:

      const obj = { "id": "A", "name": "Item A", "child": [{ "id": "B", "name": "Item B", "child": [{ "id": "C", "name": "Item C", "child": [] }] }, { "id": "D", "name": "Item D", "child": [] }] };
      
      const findObject = (obj, id) => findObjectAux([obj], id);
      const findObjectAux = (arr, id) => {
        for (const obj of arr) {
          if (obj.id === id) return obj;
          const nestedObj = findObjectAux(obj.child, id);
          if (nestedObj) return nestedObj;
        }
      }
      
      const result = findObject(obj, "C");
      console.log(result);

      或者,您可以采用查看对象本身的方法,该方法在子对象上调用.reduce(),然后在子对象上执行findObject(),以递归检查子对象/其子对象是否匹配编号:

      const obj = { "id": "A", "name": "Item A", "child": [{ "id": "B", "name": "Item B", "child": [{ "id": "C", "name": "Item C", "child": [] }] }, { "id": "D", "name": "Item D", "child": [] }] };
      
      const findObject = (obj, id) => {
        return obj.id === id 
          ? obj 
          : obj.child.reduce((acc, obj) => acc ?? findObject(obj, id), undefined);
      }
      
      const result = findObject(obj, "X");
      console.log(result);

      上述情况可以改进,因为一旦findObject() 返回非空值,.reduce() 将继续遍历返回相同累加器的元素。我们需要的是像.find() 这样的方法,一旦找到值就可以提前停止迭代。 .find() 的问题在于它只能从您迭代的数组中返回那些元素,而不是子元素。为了帮助优化这一点,您可以创建一个辅助函数(我称之为flatFind()),它的行为类似于.find(),但允许您返回一个真实值,该值将是flatFind() 返回的结果值功能:

      const obj = { "id": "A", "name": "Item A", "child": [{ "id": "B", "name": "Item B", "child": [{ "id": "C", "name": "Item C", "child": [] }] }, { "id": "D", "name": "Item D", "child": [] }, { "id": "E", "name": "Item D", "child": [] }] };
      
      const findObject = id => obj => obj.id === id 
        ? obj 
        : flatFind(obj.child, findObject(id));
      
      const result = findObject("D")(obj);
      console.log(result);
      <script>
      // Helper function:
      const flatFind = (arr, fn) => {
        for(let i = 0; i < arr.length; i++) {
          const retVal = fn(arr[i], i, arr);
          if(retVal) return retVal; // early termination
        }
      }
      </script>

      【讨论】:

      • 用优化的代码正确解释。正是我要找的。谢谢
      【解决方案3】:
      var object = {
          "id": "A",
          "name": "Item A",
          "child": [
              {
                  "id": "B",
                  "name": "Item B",
                  "child": [
                      {
                          "id": "C",
                          "name": "Item C",
                          "child": []
                      }
                  ]
              },
              {
                  "id": "D",
                  "name": "Item D",
                  "child": []
              }
          ]
      }
      
      const find_by_id = (target, object) => {
        if (object.id === target) {
          return object
        }
        for (let c of object.child) {
          let x = find_by_id(target, c) 
          if (x) {
            return x
          }
        }
      }
      
      const target = "C"
      let result = find_by_id(target, object)
      console.log(result)
      

      如果在任何情况下都失败了,请告诉我。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-02
        • 2019-03-02
        • 2023-03-26
        • 2017-12-24
        • 1970-01-01
        • 2022-08-17
        • 1970-01-01
        • 2019-03-21
        相关资源
        最近更新 更多