通过在 Javascript 中的深层嵌套对象中匹配 id 来更新对象值?

update object value by matching id within a deeply nested object in Javascript?

我有这个 Javascript 对象:

{
  title: "Securities Finance Trade Entry",
  children: [
    {
      containerType: "Tabs",
      children: [
        {
          title: "Common",
          children: [
            {
              containerType: "Row", 
              children: [
                {
                  input: "ComboBox",
                  label: "Trade Type",                  
                  options: ["Repo", "Buy/Sell", "FeeBased"],
                  value: "FeeBased"
                },
                {
                  input: "ComboBox",
                  label: "Direction",
                  options: ["Loan", "Borrow"],
                  value: "Borrow"
                }
              ]
            },
            {
              containerType: "Row",
              children: [
                {
                  containerType: "Column",
                  children: [
                    {
                      containerType: "Row",
                      children: [
                        {
                          input: "Text",
                          label: "Book",
                      value: "test"
                        },
                        {
                          input: "Text",
                          label: "Counterparty",
                      value: "test"
                        }
                      ]
                    },
                    {
                      containerType: "Row",
                      children: [
                        {
                          input: "Date",
                          label: "StartDate",
                      value: "10/02/2021"
                        },
                        {
                          input: "Date",
                          label: "EndDate",
                      value: "10/02/2021"
                        }
                      ]
                    },
                    {
                      containerType: "Row",
                      children: [
                        {
                          input: "Text",
                          label: "Security",
                      value: "test"
                        },
                        {
                          input: "Numeric",
                          label: "Quantity",
                      value: "test"
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
} 

给定一个特定的标签,我需要找到带有该标签的对象,然后替换值,我遇到的问题是这种格式可能更嵌套,我无法查看任何特定级别,我需要查看所有级别的所有对象然后只需替换标签匹配的值。我已经尝试过递归,但无法让它工作,请问有人能指点一下吗?

编辑:

这是我的尝试,但失败得很厉害:)

    const deepReplace = (obj, id, value) => {        
        if  (obj.children) {
            obj = obj.children.map((childObj) => {              
                if (typeof childObj === "object") {
                   deepReplace(childObj, id, value)
                }
            })
            return obj;
        };                
        if (obj.label === id) {
          obj['value'] = value;
          return obj
        };
     };

const newObj = deepReplace(myObj, 'TradeType', 'Repo')

这个问题似乎很适合recursion。由于要多次重复操作,不知道有多少次

  1. 基本案例如果标签存在并且与要求的相同,则更改值
  2. 如果标签不存在或不相同,则检查 属性 children 是否存在,然后遍历它并重复 1 和 2。

// passed (obj, "Book", "changed")
const replaceValueOfTheLabel = (obj, label, value) => {
  if (obj.label === label) {
    return obj.value = value;
  }

  if (obj.children)
    for (const ob of obj.children) {
      replaceValueOfTheLabel(ob, label, value)
    }

}

const obj = {
  title: "Securities Finance Trade Entry",
  children: [{
    containerType: "Tabs",
    children: [{
      title: "Common",
      children: [{
          containerType: "Row",
          children: [{
              input: "ComboBox",
              label: "Trade Type",
              options: ["Repo", "Buy/Sell", "FeeBased"],
              value: "FeeBased"
            },
            {
              input: "ComboBox",
              label: "Direction",
              options: ["Loan", "Borrow"],
              value: "Borrow"
            }
          ]
        },
        {
          containerType: "Row",
          children: [{
            containerType: "Column",
            children: [{
                containerType: "Row",
                children: [{
                    input: "Text",
                    label: "Book",
                    value: "test"
                  },
                  {
                    input: "Text",
                    label: "Counterparty",
                    value: "test"
                  }
                ]
              },
              {
                containerType: "Row",
                children: [{
                    input: "Date",
                    label: "StartDate",
                    value: "10/02/2021"
                  },
                  {
                    input: "Date",
                    label: "EndDate",
                    value: "10/02/2021"
                  }
                ]
              },
              {
                containerType: "Row",
                children: [{
                    input: "Text",
                    label: "Security",
                    value: "test"
                  },
                  {
                    input: "Numeric",
                    label: "Quantity",
                    value: "test"
                  }
                ]
              }
            ]
          }]
        }
      ]
    }]
  }]
}

replaceValueOfTheLabel(obj, "Book", "changed");

console.log(obj);

如果您对不改变原始数据但 return 替换了适当值的副本感兴趣,它可能看起来像这样:

const deepReplace = (label, value) => (obj) => Object (obj) === obj
  ? { ... obj, 
      ... (obj .label == label ? {value} : {}), 
      ... (obj .children ? {children: obj .children .map (deepReplace (label, value))} : {})
    }
  : obj

const input = {title: "Securities Finance Trade Entry", children: [{containerType: "Tabs", children: [{title: "Common", children: [{containerType: "Row", children: [{input: "ComboBox", label: "Trade Type", options: ["Repo", "Buy/Sell", "FeeBased"], value: "FeeBased"}, {input: "ComboBox", label: "Direction", options: ["Loan", "Borrow"], value: "Borrow"}]}, {containerType: "Row", children: [{containerType: "Column", children: [{containerType: "Row", children: [{input: "Text", label: "Book", value: "test"}, {input: "Text", label: "Counterparty", value: "test"}]}, {containerType: "Row", children: [{input: "Date", label: "StartDate", value: "10/02/2021"}, {input: "Date", label: "EndDate", value: "10/02/2021"}]}, {containerType: "Row", children: [{input: "Text", label: "Security", value: "test"}, {input: "Numeric", label: "Quantity", value: "test"}]}]}]}]}]}]}

console .log (deepReplace ('Counterparty', '*** New value ***') (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

我们测试我们的输入是否是一个对象。如果没有,我们就简单地 return 吧。如果是,我们通过包含对象的所有根属性来创建一个新对象,替换一个 value 属性 如果我们有权利 label,然后在 [=20] 上重复=]节点。

不可变数据有各种各样的好处。我建议您尽可能采用它。

更新:代码说明

有评论认为这很难阅读。这是我试图解释的。

const deepReplace = (label, value) => (obj) => /* ... */

首先,我们柯里化函数,传递 labelvalue 以取回另一个函数,该函数采用 obj 和 returns 一个对象并进行了更改。这在内部(我们稍后会将对 deepReplace (label, value) 的引用传递给 map)和外部都很有用,因为我们现在可以只传递标签和值并获得一个可以应用于许多不同对象的函数。

然后我们开始条件运算符(三元)。我们测试输入参数是否是一个对象,使用Object (obj) === obj。有很多方法可以对此进行测试,包括 typeof 和对值调用 Object.prototype.toString()。我觉得这个很结实。

如果不是对象,那么我们打最后一行: obj,return原始值。如果它是一个对象,我们 return 分三步创建一个对象:

    ... obj,

这里我们只是将原始对象传播到我们的新对象中。这是一种浅克隆形式。

    ... (obj .label == label ? {value} : {}), 

这里我们测试对象的 label 属性 是否匹配我们的目标。如果是这样,我们将 {value} 传播到我们的对象中,对于 {value: value} 来说,这是现代的 shorthand。如果没有,我们将展开一个空对象,从而不添加任何属性。

    ... (obj .children ? {children: obj .children .map (deepReplace (label, value))} : {})

在这里,我们处理 children。如果我们的节点没有children属性,我们散布一个空对象。如果是这样,我们 map 对它们进行相同函数的递归调用,并散布在一个对象中,其结果为 children 属性。这里柯里化很有帮助,因为我们可以调用 map (deepReplace (label, value)) 如果我们的函数看起来像 deepReplace = (label, value, obj) => /* ... */,那么我们必须调用 map ((item) => deepReplace (label, value, item))。没什么大不了的,但这样感觉更干净。

所以在旧的 JS 中,我们可能会编写这样的等效代码:

const deepReplace = (label, value) => (obj) => {
  if (Object (obj) !== obj) {
    return obj
  }

  const newObject = {...obj}

  if (obj .label == label) {
    newObject .value = value
  }

  if ('children' in obj) {
    newObject .children = obj .children .map (deepReplace (label, value))
  }
  return newObject
}

这在本质上是等价的。我发现较新的语法更有启发性,但两者都可以。


我们可能应该处理数组的情况,这会给实现增加一点,(未经测试的)版本可能如下所示:

const deepReplace = (label, value) => (obj) => Array .isArray (obj)
  ? obj .map (deepReplace (label, value)) 
  : Object (obj) === obj
    ? { ... obj, 
        ... (obj .label == label ? {value} : {}), 
        ... (obj .children ? {children: obj .children .map (deepReplace (label, value))} : {})
      }
    : obj

这里我们首先检查一个数组,如果是,map 结果,就像我们对 children 所做的那样。