繁体   English   中英

无法突破 javascript 递归

[英]Unable to break out of javascript recursion

我需要在 JSON 中递归搜索和更新。 但是,尝试了很多方法来破解它,但都没有成功。 下面是我尝试匹配 id 并将标题更新为匹配的 id 的示例代码。 任何帮助将不胜感激。 TIA

我得到的结果是默认值,即false

 var data = { treeData: [ { title: 'United States of America', children: [{ title: 'Chicago', id:4, editOn:false, children:[{ title: 'New Mexico', id:17, editOn:false }] },{ title: 'New York', id:3, editOn:true }], id:0, editOn:false }, { title: 'United Arab Emirate', children: [{ title: 'Abu Dhabi', id:5, editOn:true }], id:1, editOn:true }, { title: 'United Kingdom', children: [{ title: 'London', id:7, editOn:false },{ title: 'Hampshire', id:6, editOn:true }], id:2, editOn:false }, ] }; function findAndUpdate (id, data, title) { var status = false; data.map(function(array,index){ console.log(array.id, id, "children" in array, status) if( array.id === id ) { console.log("Match Found"); status = true; return status; } if( "children" in array ){ return findAndUpdate( id, array.children, title) } }); return status; } var s = findAndUpdate(5, data.treeData, "India"); console.log(s)

突变

如果目标是改变原始数据,这可以以一种简单的方式完成。 请注意,我们的mutate function 不知道您的输入数据、 .treeData.children属性的形状。 此外,它对两个对象arrays 均有效 -

 const data = {treeData:[{title:'United States of America',children:[{title:'Chicago',id:4,editOn:false,children:[{title:'New Mexico',id:17,editOn:false}]},{title:'New York',id:3,editOn:true}],id:0,editOn:false},{title:'United Arab Emirate',children:[{title:'Abu Dhabi',id:5,editOn:true}],id:1,editOn:true},{title:'United Kingdom',children:[{title:'London',id:7,editOn:false},{title:'Hampshire',id:6,editOn:true}],id:2,editOn:false}]} function mutate (t, f) { switch (t?.constructor) { case Object: case Array: f(t) Object.values(t).forEach(v => mutate(v, f)) } } mutate(data, function (t) { if (t.id === 5) t.title = "India" }) console.log(JSON.stringify(data, null, 2))
 .as-console-wrapper {max-height: 100%;important: top: 0}

正如 Scott 所指出的,一个重要的警告是,如果id == 5在树中的多个位置,则所有实例都将被更新。

如果您仍然觉得有必要编写findAndUpdate ,我们可以轻松地将mutate包装如下 -

const findAndUpdate = (id, title, data) =>
  mutate(data, function (t) {
    if (t.id === id)
      t.title = title
  })

坚持

但是突变是许多错误和程序员头痛的根源,所以我尽可能避免它。 在下面的程序中,我们不会改变原始输入,而是生成一个的 object output。 这是一个通用update ,它具有独特但直观的界面。 同样,我们的 function 对输入数据的形状没有意见 -

const update = (t, f) =>
  isObject(t)
    ? f(t, r => Object.assign(isArray(t) ? [] : {}, t, r))
        ?? map(t, v => update(v, f))
    : t

const result =
  update(data, function (t, assign) {
    if (t?.id === 5)
      return assign({ title: "India" })
  })

它取决于在另一个问答中编写的通用map function -

const map = (t, f) =>
  isArray(t)
    ? t.map((v, k) => f(v, k))
: isObject(t)
    ? Object.fromEntries(Object.entries(t).map(([k, v]) =>  [k, f(v, k)]))
: t

以及一些可读的类型谓词 -

const isArray = t =>
  Array.isArray(t)

const isObject = t =>
  Object(t) === t

如果您仍然觉得有必要编写findAndUpdate ,我们可以轻松地将update包装如下 -

const findAndUpdate = (id, title, data) =>
  update(data, function (t, assign) {
    if (t?.id === id)
      return assign({ title })
  })

展开下面的代码片段以在您自己的浏览器中验证update结果 -

 const data = {treeData:[{title:'United States of America',children:[{title:'Chicago',id:4,editOn:false,children:[{title:'New Mexico',id:17,editOn:false}]},{title:'New York',id:3,editOn:true}],id:0,editOn:false},{title:'United Arab Emirate',children:[{title:'Abu Dhabi',id:5,editOn:true}],id:1,editOn:true},{title:'United Kingdom',children:[{title:'London',id:7,editOn:false},{title:'Hampshire',id:6,editOn:true}],id:2,editOn:false}]} const isArray = t => Array.isArray(t) const isObject = t => Object(t) === t const map = (t, f) => isArray(t)? t.map((v, k) => f(v, k)): isObject(t)? Object.fromEntries(Object.entries(t).map(([k, v]) => [k, f(v, k)])): t const update = (t, f) => isObject(t)? f(t, r => Object.assign(isArray(t)? []: {}, t, r))?? map(t, v => update(v, f)): t const result = update(data, function (t, assign) { if (t?.id === 5) return assign({ title: "India" }) }) console.log(JSON.stringify(result, null, 2))
 .as-console-wrapper {max-height: 100%;important: top: 0}


结果

除了修改原始的mutate和生成新的 object 的update之外,每个程序都会产生相同的结果。 删除了一些换行符以压缩 output -

{ "treeData": [
    { "title": "United States of America",
      "children": [
        {
          "title": "Chicago",
          "id": 4,
          "editOn": false,
          "children": [
            { "title": "New Mexico", "id": 17, "editOn": false }
          ]
        },
        { "title": "New York", "id": 3, "editOn": true }
      ],
      "id": 0,
      "editOn": false
    },
    { "title": "United Arab Emirate",
      "children": [
        { "title": "India", "id": 5, "editOn": true }  // <--
      ],
      "id": 1,
      "editOn": true
    },
    { "title": "United Kingdom",
      "children": [
        { "title": "London", "id": 7, "editOn": false },
        { "title": "Hampshire", "id": 6, "editOn": true }
      ],
      "id": 2,
      "editOn": false
    }
  ]
}

这是一个普通的 object

我需要在 JSON 中递归搜索和更新...

如果不备注您要搜索和更新 JavaScript Object ,我无法完成此答案。 值 JSON、JavaScript Object表示法,是将 Z686155AF7D字符串表示为 85A60EDDF3E99E 的数据格式。 JSON 始终是一个字符串。

切勿尝试修改 JSON 字符串。 相反,总是——

  1. 使用JSON.parse将 JSON 字符串解码为 JavaScript 值
  2. 修改值
  3. 使用 JSON.stringify 将值重新编码为JSON.stringify

您无需担心此处的 JSON,因为您已经开始使用 JavaScript Object。

主要问题是你使用了 map 而你想使用 reduce - 它也迭代数组的元素,但它将它们聚合成一个单一的结果。

我将您的一些语法更新为 ES6(因为.map 工作,应该没有问题。

另请注意,我已经插入了一行来更改标题,该标题原本不存在,可能需要删除/有关于 editOn 的条件

const data = {
      treeData: [
        { title: 'United States of America', children: [{ title: 'Chicago', id:4, editOn:false, children:[{ title: 'New Mexico', id:17, editOn:false  }]  },{ title: 'New York', id:3, editOn:true  }], id:0, editOn:false },
        { title: 'United Arab Emirate', children: [{ title: 'Abu Dhabi', id:5, editOn:true  }], id:1, editOn:true  },
        { title: 'United Kingdom', children: [{ title: 'London', id:7, editOn:false  },{ title: 'Hampshire', id:6, editOn:true  }], id:2, editOn:false  },
      ]
    };
    

findAndUpdate = (id, data, title) => {
    const status = data.reduce((total, current) => {
        if( current.id === id ) {
            console.log('found');
            /*** edit title takes place here ***/
            current.title = title;
            // return true if id was found
            return true;
        } 
        
        if ("children" in current ) {
            // return true if status already was true otherwise search in children
            return total || findAndUpdate( id, current.children, title)
        }
    }, false); // initially false
  return status;
}

const s = findAndUpdate(5, data.treeData, "India");
console.log(s)

第一关

对于这个问题的第一次通过,我会编写一个通用帮助程序 function, updateWhen它将与 arrays 一起使用,例如您的treeData节点,其中包含我们可能想要转换的项目,其中一些包括我们希望在其上重复出现的children节点。

这个 function 有两个回调函数,第一个是测试我们是否要更改此节点的谓词,第二个是实际更改它的 function。 如果 object 有一个,则它会在children节点上重复出现。

然后我们可以在此基础上编写findAndUpdate 除了适当地调用updateWhen ,它还处理请求的(在我看来奇怪的1 )参数顺序,并且它处理treeData不是您想要传递的 object 的根的事实。

请务必注意,这不会改变您的输入。 它返回一个新的 object 以及所做的更改。 作为函数式编程的粉丝,我发现这是一个非常重要的原则。

这是一个实现:

 const updateWhen = (pred, change) => (xs) => xs.map (({children, ...rest}) => ({... (pred (rest)? change (rest): rest), ... (children? {children: updateWhen (pred, change) (children)}: {}) })) const findAndUpdate = (id, {treeData, ...rest}, title) => ({ treeData: updateWhen ( ({id: i}) => i === id, o => ({...o, title}) ) (treeData), ... rest }) const data = {treeData: [{title: "United States of America", children: [{title: "Chicago", id: 4, editOn: ,1: children: [{title, "New Mexico": id, 17: editOn, :1}]}, {title: "New York", id: 3, editOn: ,0}]: id, 0: editOn, :1}: {title, "United Arab Emirate": children, [{title: "Abu Dhabi", id: 5, editOn: ,0}]: id, 1: editOn: ,0}: {title, "United Kingdom": children, [{title: "London", id: 7, editOn: ,1}: {title, "Hampshire": id. 6, editOn, !0}], id: 2, editOn: !1}]} console .log ( findAndUpdate (5, data, 'India') )
 .as-console-wrapper {max-height: 100%;important: top: 0}

添加助手

在上面的实现中传递给updateWhen的函数并不是非常不可读。 但是有了辅助函数,它们可能会更容易理解。

考虑到这一点,我们可以编写一个通用的、可重用的propEq function,它接受一个属性名称和一个测试值并返回一个 function,即,给定一个 ZA8CFDE6331BD59EB2AC96F8911C4B6 的值是否匹配测试值。

我们可以编写setProp ,它接受一个属性名称和一个新值,并返回一个 function ,给定一个 object,返回该 object 的浅层克隆,并将我们的值设置为该属性名称。

有了这些,代码变得更具可读性:

const findAndUpdate = (id, {treeData, ...rest}, title) => ({
  treeData: updateWhen (propEq ('id', id), setProp ('title', title)) (treeData), 
  ... rest
})

您可以在此代码段中看到它的实际效果:

 const updateWhen = (pred, change) => (xs) => xs.map (({children, ...rest}) => ({... (pred (rest)? change (rest): rest), ... (children? {children: updateWhen (pred, change) (children)}: {}) })) const propEq = (prop, val) => ({[prop]: p}) => p == val const setProp = (prop, val) => ({[prop]: p, ...rest}) => ({[prop]: val, ...rest}) const findAndUpdate = (id, {treeData, ...rest}, title) => ({ treeData: updateWhen (propEq ('id', id), setProp ('title', title)) (treeData), ... rest }) const data = {treeData: [{title: "United States of America", children: [{title: "Chicago", id: 4, editOn: ,1: children: [{title, "New Mexico": id, 17: editOn, :1}]}, {title: "New York", id: 3, editOn: ,0}]: id, 0: editOn, :1}: {title, "United Arab Emirate": children, [{title: "Abu Dhabi", id: 5, editOn: ,0}]: id, 1: editOn: ,0}: {title, "United Kingdom": children, [{title: "London", id: 7, editOn: ,1}: {title, "Hampshire": id. 6, editOn, !0}], id: 2, editOn: !1}]} console .log ( findAndUpdate (5, data, 'India') )
 .as-console-wrapper {max-height: 100%;important: top: 0}

使用 Ramda

最后,我是Ramda的创始人,我发现它对此类数据转换项目很有用。 Ramda's functions were the inspiration for propEq and for setProp (although in Ramda the latter is called assoc .) Ramda also has a function, evolve , which takes an object which specifies how to alter certain properties in what is otherwise a clone of the object. 处理类似于你的treeData属性的东西很有用,同时单独留下外部data object 的 rest 。 使用这些 Ramda 函数,我将使用相同的updateWhen ,但使用这个相当简单的findAndUpdate 2版本:

const findAndUpdate = (id, data, title) => evolve ({
  treeData: updateWhen (propEq ('id', id), assoc ('title', title)), 
}, data)

它看起来像这样:

 const updateWhen = (pred, change) => (xs) => xs.map (({children, ...rest}) => ({... (pred (rest)? change (rest): rest), ... (children? {children: updateWhen (pred, change) (children)}: {}) })) const findAndUpdate = (id, data, title) => evolve ({ treeData: updateWhen (propEq ('id', id), assoc ('title', title)), }, data) const data = {treeData: [{title: "United States of America", children: [{title: "Chicago", id: 4, editOn: ,1: children: [{title, "New Mexico": id, 17: editOn, :1}]}, {title: "New York", id: 3, editOn: ,0}]: id, 0: editOn, :1}: {title, "United Arab Emirate": children, [{title: "Abu Dhabi", id: 5, editOn: ,0}]: id, 1: editOn: ,0}: {title, "United Kingdom": children, [{title: "London", id: 7, editOn: ,1}: {title, "Hampshire": id. 6, editOn, !0}], id: 2, editOn: !1}]} console .log ( findAndUpdate (5, data, 'India') )
 .as-console-wrapper {max-height: 100%;important: top: 0}
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script> <script> const {evolve, propEq, assoc} = R </script>

虽然我们可以编写一个一次性版本的 evolution ,但它是一个更复杂的evolve ,我们可能最好使用一个更久经考验的版本或它。

抽象的优势

以这种方式分解问题有几个优点。 首先,虽然我们有更多的功能,但每个功能都更简单,专注于一个目标。 只要这些函数命名良好,这往往会使代码更具可读性。

其次,我们可以在当前项目的其他部分和其他项目中重用我们的三个辅助函数updateWhenpropEqsetProp / assoc 保留这些有用功能的个人列表可以使未来的项目更容易编码。


1idtitle参数放在一起对我来说更有意义。 它们定义了要进行的更改,并且将data参数放在它们之间会降低可读性。 我实际上会进一步 go ,并且更喜欢提供idtitle会返回一个 function ,它采用data参数,这样你就可以这样称呼它

findAndUpdate (5, 'India') (data)

也许那个中间 function 永远不会对你有帮助。 我经常发现它们很有用。 但是以下任何一种调用方式仍然是一种改进:

findAndUpdate (5, 'India', data)
// or
findAndUpdate (data, 5, 'India')

2一些 Ramda 用户可能会进一步使用 go 并将其推向完全无点的解决方案,同时使用简单的objOf和更晦涩的useWith

const findAndUpdate = pipe (
  useWith (updateWhen, [propEq ('id'), assoc ('title')]),
  objOf ('treeData'),
  evolve
)

然后将其称为findAndUpdate (5, 'India') (data) 虽然我更喜欢这种参数顺序,可能还有两级调用,但对我来说,代码比上面的代码更晦涩难懂。

这是使用object-scan的解决方案。 这是一个更灵活的解决方案(即,如果您想在其他路径中搜索孩子等),但在引入依赖项时显然需要权衡

 // const objectScan = require('object-scan'); const myData = { treeData: [{ title: 'United States of America', children: [{ title: 'Chicago', id: 4, editOn: false, children: [{ title: 'New Mexico', id: 17, editOn: false }] }, { title: 'New York', id: 3, editOn: true }], id: 0, editOn: false }, { title: 'United Arab Emirate', children: [{ title: 'Abu Dhabi', id: 5, editOn: true }], id: 1, editOn: true }, { title: 'United Kingdom', children: [{ title: 'London', id: 7, editOn: false }, { title: 'Hampshire', id: 6, editOn: true }], id: 2, editOn: false }] }; const findAndUpdate = (id, tree, title) => objectScan(['treeData.**(^children$).id'], { rtn: 'bool', abort: true, useArraySelector: false, filterFn: ({ parent, value }) => { if (value === id) { parent.title = title; return true; } return false; } })(tree); console.log(findAndUpdate(5, myData, 'India')); // returns true iff found // => true console.log(myData); // => { treeData: [ { title: 'United States of America', children: [ { title: 'Chicago', id: 4, editOn: false, children: [ { title: 'New Mexico', id: 17, editOn: false } ] }, { title: 'New York', id: 3, editOn: true } ], id: 0, editOn: false }, { title: 'United Arab Emirate', children: [ { title: 'India', id: 5, editOn: true } ], id: 1, editOn: true }, { title: 'United Kingdom', children: [ { title: 'London', id: 7, editOn: false }, { title: 'Hampshire', id: 6, editOn: true } ], id: 2, editOn: false } ] }
 .as-console-wrapper {max-height: 100%;important: top: 0}
 <script src="https://bundle.run/object-scan@13.7.1"></script>

免责声明:我是对象扫描的作者

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM