【问题标题】:Filter / reduce nested object recursively递归过滤/减少嵌套对象
【发布时间】:2019-09-15 08:24:23
【问题描述】:

我有一个深度嵌套的对象,我需要搜索它以删除某些键。要删除的键存储在removeKeys 数组中指示的数组中。 目前,该函数仅过滤顶级对象,但可以很好地缩放其余对象,它只是不过滤子对象。我将如何正确减少整个对象以获得所需的输出?

初始未过滤对象:

let item = {
            "label": "test",
            "id": "test",
            "styles": {
                "label": "Styles",
                "styles": {
                    "test": {
                        "test": "test",
                        "label": "test",
                        "test1": {
                            "label": "test",
                            "image": {
                                "label": "test",
                                "type": "test",
                                "value": "test",
                                "autoSelect": "",
                                "id": ""
                            }
                        }
                    }
                }
            },
            "test": {
                "label": "test",
                "test": []
            }
        }

要从对象中删除的键:

const removeKeys = ["label", "type", "autoSelect"];

过滤嵌套对象的递归函数:

let filterObject = filterNestObject(item);

function filterNestObject(item) {
  return Object.keys(item)
  .filter(key => {
    if (typeof item[key] === 'object') filterNestObject(item[key]);

    if (!removeKeys.includes(key)) return true;

    return false 
  })  
  .reduce((object, key) => {
    return {
      ...object,
      [key]: item[key]
    };
  }, {});

}

预期的结果是:

{
            "id": "test",
            "styles": {
                "styles": {
                    "test": {
                        "test": "test",
                        "test1": {
                            "image": {
                                "value": "test",
                                "id": ""
                            }
                        }
                    }
                }
            },
            "test": {
                "test": []
            }
        }

【问题讨论】:

    标签: javascript arrays object recursion filter


    【解决方案1】:

    我可能会使用Object.entriesfilter + includesmapObject.fromEntries -

    const removeDeepKeys = (keys = [], o = {}) =>
      Object (o) === o
        ? Object
            .fromEntries
              ( Object
                  .entries (o)
                  .filter (([ k, _ ]) => ! keys .includes (k))
                  .map (([ k, v ]) => [ k, removeDeepKeys (keys, v) ])
              )
        : o
    

    在你的 item 上试试 -

    removeDeepKeys ([ 'label', 'type', 'autoSelect' ], item)
    

    输出 -

    {
      "id": "test",
      "styles": {
        "styles": {
          "test": {
            "test": "test",
            "test1": {
              "image": {
                "value": "test",
                "id": ""
              }
            }
          }
        }
      },
      "test": {
        "test": {}
      }
    }
    

    编辑以支持数组 -

    const removeDeepKeys = (keys = [], o = {}) =>
      Array .isArray (o)
        ? o .map (v => removeKeys (keys, v))
        : Object (o) === o
            ? Object
                .fromEntries
                  ( Object
                      .entries (o)
                      .filter (([ k, _ ]) => ! keys .includes (k))
                      .map (([ k, v ]) => [ k, removeDeepKeys (keys, v) ])
                  )
            : o
    

    【讨论】:

      【解决方案2】:

      您的代码中的错误是您在filter 回调中进行了递归调用。但是在那里你丢失了从递归调用返回的对象。而是在 reduce 回调中进行。

      一个小的修正:要测试一个值是否是一个对象,typeof item[key] === "object" 是不够的,因为null 也可以通过该测试。这是修改后的代码:

      function filterNestObject(item) {
          return Object.keys(item)
              .filter(key => !removeKeys.includes(key))  
              .reduce((acc, key) => {
                  return Object.assign(acc, {
                    [key]: Object(item[key]) === item[key] ? filterNestObject(item[key]) : item[key]
                  });
              }, Array.isArray(item) ? [] : {});
      }
      
      const item = {"label": "test","id": "test","styles": {"label": "Styles","styles": {"test": {"test": "test","label": "test","test1": {"label": "test","image": {"label": "test","type": "test","value": "test","autoSelect": "","id": ""}}}}},"test": {"label": "test","test": []}};
      const removeKeys = ["label", "type", "autoSelect"];
      const filterObject = filterNestObject(item);
      console.log(filterObject);

      【讨论】:

        【解决方案3】:

        前段时间,我尝试创建一个cloneObj() 方法来使用新提案Object.fromEntries() 深度克隆一个对象。您可以在下一个链接查看我当时提出的问题以供参考:Deep-Cloning an object using Object.fromEntries()

        我相信可以稍微修改此方法以适应您的目标:

        const item = {"label": "test","id": "test","styles": {"label": "Styles","styles": {"test": {"test": "test","label": "test","test1": {"label": "test","image": {"label": "test","type": "test","value": "test","autoSelect": "","id": ""}}}}},"test": {"label": "test","test": [{label: "foo", test: "test4"}]}};
        const removeKeys = ["label", "type", "autoSelect"];
        
        const cloneObjWithoutKeys = (obj, keys) =>
        {
            if (Object(obj) !== obj)
               return obj;
            else if (Array.isArray(obj))
               return obj.map(o => cloneObjWithoutKeys(o, keys));
        
            return Object.fromEntries(
                Object.entries(obj)
                      .filter(([k, v]) => !keys.includes(k))
                      .map(([k, v]) => ([k, cloneObjWithoutKeys(v, keys)])
            ));
        }
        
        console.log(cloneObjWithoutKeys(item, removeKeys));
        .as-console {background-color:black !important; color:lime;}
        .as-console-wrapper {max-height:100% !important; top:0;}

        注意,这也会遍历对象的内部数组以过滤掉所需的键。

        【讨论】:

        • 这是唯一没有将数组转换为对象的解决方案。感谢您假设边缘情况。
        • 谢谢,但请注意,Object.fromEntries() 目前还没有得到很好的支持。
        【解决方案4】:

        您可以通过采用迭代和递归的方法过滤键并构建新对象。

        function remove(object, keys) {
            return Object.assign({}, ...Object.keys(object)
                .filter(k => !keys.includes(k))
                .map(k => ({ [k]: object[k] && typeof object[k] === 'object' ? remove(object[k], keys) : object[k] }))
            );
        }
        
        var item = { label: "test", id: "test", styles: { label: "Styles", styles: { test: { test: "test", label: "test", test1: { label: "test", image: { label: "test", type: "test", value: "test", autoSelect: "", id: "" } } } } }, test: { label: "test", test: [] } },
            removeKeys = ["label", "type", "autoSelect"];
        
        console.log(remove(item, removeKeys));
        .as-console-wrapper { max-height: 100% !important; top: 0; }

        【讨论】:

          【解决方案5】:

          您正在递归调用该函数,但您对递归调用返回的结果没有做任何事情。您必须用过滤后的值覆盖子键:

          let item = {
            "label": "test",
            "id": "test",
            "styles": {
              "label": "Styles",
              "styles": {
                "test": {
                  "test": "test",
                  "label": "test",
                  "test1": {
                    "label": "test",
                    "image": {
                      "label": "test",
                      "type": "test",
                      "value": "test",
                      "autoSelect": "",
                      "id": ""
                    }
                  }
                }
              }
            },
            "test": {
              "label": "test",
              "test": []
            }
          }
          
          const removeKeys = ["label", "type", "autoSelect"];
          
          let filterObject = filterNestObject(item);
          
          function filterNestObject(item) {
            return Object.keys(item)
              .filter(key => {
                if (typeof item[key] === 'object') {
                  // set the key to the filtered result returned by the recursively called function
                  item[key] = filterNestObject(item[key]);
                }
          
                if (!removeKeys.includes(key)) return true;
          
                return false
              })
              .reduce((object, key) => {
                return {
                  ...object,
                  [key]: item[key]
                };
              }, {});
          
          }
          
          console.log(filterNestObject(item));

          【讨论】:

            【解决方案6】:

            它有点 hacky 并且性能不是很好,所以如果您要处理非常大的对象图,它可能不是一个好的解决方案,但这里有一个使用replacer 回调在JSON.stringify 中的单线解决方案:

            JSON.parse(JSON.stringify(audience, (k, v) => removeKeys.includes(k) ? undefined : v));
            

            演示:

            let audience = {
              "label": "test",
              "id": "test",
              "styles": {
                "label": "Styles",
                "styles": {
                  "test": {
                    "test": "test",
                    "label": "test",
                    "test1": {
                      "label": "test",
                      "image": {
                        "label": "test",
                        "type": "test",
                        "value": "test",
                        "autoSelect": "",
                        "id": ""
                      }
                    }
                  }
                }
              },
              "test": {
                "label": "test",
                "test": []
              }
            }
            const removeKeys = ["label", "type", "autoSelect"];
            let newAudience = JSON.parse(JSON.stringify(audience, (k, v) => removeKeys.includes(k) ? undefined : v));
            console.log(newAudience);

            同样,如果您从 JSON 字符串解析原始对象,则可以使用来自 JSON.parsereviver 回调:

            let jsonString = `{
              "label": "test",
              "id": "test",
              "styles": {
                "label": "Styles",
                "styles": {
                  "test": {
                    "test": "test",
                    "label": "test",
                    "test1": {
                      "label": "test",
                      "image": {
                        "label": "test",
                        "type": "test",
                        "value": "test",
                        "autoSelect": "",
                        "id": ""
                      }
                    }
                  }
                }
              },
              "test": {
                "label": "test",
                "test": []
              }
            }`
            const removeKeys = ["label", "type", "autoSelect"];
            const audience = JSON.parse(jsonString, (k, v) => removeKeys.includes(k) ? undefined : v);
            console.log(audience);

            【讨论】:

            • 这里的缺点是它只适用于简单的数据类型。一旦输入数据具有SetMapDateRegExp 等值,它将无法工作。
            • @trincot 正确,这仅适用于可序列化到/来自 JSON 的值。正如我所说,这是一种 hack,并不是适用于所有场景的正确解决方案。
            • 对于具有严格数据类型的对象的正确场景非常有趣。
            猜你喜欢
            • 1970-01-01
            • 2019-02-11
            • 2012-02-12
            • 1970-01-01
            • 2020-09-18
            • 2020-07-28
            相关资源
            最近更新 更多