繁体   English   中英

在 JavaScript 中递归遍历数组并修改对象属性的值

[英]Traversing recursively through an array and modifying values of object properties in JavaScript

这篇文章可能看起来很长,但很容易理解,如果没有,我会添加更多细节

我有看起来像的criteria数组:

   let criteria = [
        "and",
        {
          "Collection": "persons",
          "Property": "phone",
          "operator": "eq",
          "operatorValue": "$p:phone"  
        },
        {
          "Collection": "persondetails",
          "Property": "country",
          "operator": "eq",
          "operatorValue": "$p:country" 
        },
        ["or",
        {
          "Collection": "persons",
          "Property": "city",
          "operator": "eq",
          "operatorValue": "$p:city"  
        }]
      ]

criteria特点:

  1. 它可能有嵌套数组。
  2. 第一项数组(或嵌套数组)始终是“and”或“or”
  3. 数组中的第二个项目,项目可以是具有此特定结构的对象

{ "Collection": "persons", "Property": "phone", "operator": "eq", "operatorValue": "$p:phone" }

或者它可以是一个数组,如:

["or", { "Collection": "persons", "Property": "city", "operator": "eq", "operatorValue": "$p:city" }]

  1. 对象永远不会是嵌套对象

还有一个parameters对象:

let parameters = {phone:"23138213", "country": "Russia", "city":"york"}

目的是递归遍历criteria数组中的所有operatorValue属性,如果遇到诸如$p:phone ,它将被替换为任何parameters["phone"]评估为。

预期产出:

[
    "and",
    {
      "Collection": "persons",
      "Property": "phone",
      "operator": "eq",
      "operatorValue": "23138213"  
    },
    {
      "Collection": "persondetails",
      "Property": "country",
      "operator": "eq",
      "operatorValue": "Russia" 
    },
    ["or",
    {
      "Collection": "persons",
      "Property": "city",
      "operator": "eq",
      "operatorValue": "york"  
    }]
  ]

我能够递归遍历数组。 唯一的问题是我不知道如何修改原始criteria变量。

复制代码

请参阅 repl 中的第 43 行。 item[1]=parameters[item[1].split('$p:')[1]]我理解为什么它不会修改标准,因为这里的 item 是完全不同范围内的不同变量。

失败的尝试:

  function traverse(obj,parameters){
  
    obj.forEach((item,index)=>{
      
      
     if( typeof item == 'string' ){
       //do nothing
     }
     else if( !(item instanceof Array)){
         
       Object.entries(item).forEach((item,index)=>{
         
         if( item[1] instanceof Array){ 
                      
           traverse(item,parameters);
         }else{
           if(item[1].startsWith('$p:')){
               item[1]=parameters[item[1].split('$p:')[1]] //values dont get replaced for obvious reason
               console.log(item[1])
           } 
         }
       })        
     }
     else if( item  instanceof Array){           
           traverse(item,parameters);
      }  
    })
  }

  traverse(criteria,parameters)
  console.log(criteria)

我该如何解决这个问题?

你可以简化你的功能。 您不需要遍历对象的条目。 您也不需要split operationValue parameters映射键存在于Property键中。

  • 循环遍历数组中的每个项目并检查该项目是否为Array
  • 如果是,则递归调用该项目的traverse
  • 如果它是一个对象,用parameters[val.Property]更新它的operatorValue属性

 function traverse(arr, parameters) { for (const item of arr) { if (Array.isArray(item)) traverse(item, parameters) else if (typeof item === 'object') item.operatorValue = parameters[item.Property] } return arr } let criteria=["and",{Collection:"persons",Property:"phone",operator:"eq",operatorValue:"$p:phone"},{Collection:"persondetails",Property:"country",operator:"eq",operatorValue:"$p:country"},["or",{Collection:"persons",Property:"city",operator:"eq",operatorValue:"$p:city"}]], parameters = {phone:"23138213", "country": "Russia", "city":"york"}; console.log(traverse(criteria, parameters))

您需要获取对象的键来分配值。

 function traverse(obj, parameters) { obj.forEach(item => { if (item instanceof Array) return traverse(item, parameters); if (item?.operatorValue?.startsWith('$p:')) { item.operatorValue = parameters[item.operatorValue.split('$p:')[1]]; } }); } const criteria = ["and", { Collection: "persons", Property: "phone", operator: "eq", operatorValue: "$p:phone" }, { Collection: "persondetails", Property: "country", operator: "eq", operatorValue: "$p:country" }, ["or", { Collection: "persons", Property: "city", operator: "eq", operatorValue: "$p:city" }]], parameters = { phone: "23138213", "country": "Russia", "city": "york" }; traverse(criteria, parameters); console.log(criteria);
 .as-console-wrapper { max-height: 100% !important; top: 0; }

一个干净的递归版本:

 const traverse = ([conj, ...nodes], params) => [ conj, ... nodes .map (node => Array .isArray (node) ? traverse (node, params) : {... node, operatorValue: params [nodes .Property]} ) ] const criteria = ["and", {Collection: "persons", Property: "phone", operator: "eq",operatorValue: "$p:phone"}, {Collection: "persondetails", Property: "country", operator: "eq", operatorValue: "$p:country"}, ["or", {Collection: "persons", Property: "city", operator: "eq", operatorValue: "$p:city"}]] const parameters = {phone:"23138213", "country": "Russia", "city":"york"} console .log (traverse (criteria, parameters))
 .as-console-wrapper {min-height: 100% !important; top: 0}

这仅在 adiga 的简化证明对象的Property节点与operatorValue的后缀具有相同的值时才有效。 如果没有,我可能会使用正则表达式替换来处理它,并将其分成一个辅助函数:

const replaceOpValue = (params) => (obj) => ({
  ... obj, 
  operatorValue: obj .operatorValue .replace (/\$p:(.+)/, ((_, key) => params [key]))
})
  

const traverse = ([conj, ...nodes], params) => [
  conj, 
  ... nodes .map (node => Array .isArray (node) 
        ? traverse (node, params)
        : replaceOpValue (params) (node)
      )
]

这是使用object-scan的解决方案。 根据您的要求,可能会更加灵活和可维护

 // const objectScan = require('object-scan'); const criteria = [ 'and', { Collection: 'persons', Property: 'phone', operator: 'eq', operatorValue: '$p:phone' }, { Collection: 'persondetails', Property: 'country', operator: 'eq', operatorValue: '$p:country' }, [ 'or', { Collection: 'persons', Property: 'city', operator: 'eq', operatorValue: '$p:city' } ] ]; const substitute = (obj, params) => objectScan(['**.operatorValue'], { rtn: 'count', filterFn: ({ value, parent, property }) => { if (value.startsWith('$p:') && value.slice(3) in params) { parent[property] = params[value.slice(3)]; return true; } return false; } })(obj); console.log(substitute(criteria, { phone: '23138213', country: 'Russia', city: 'york' })); // returns number of substitutions // => 3 console.log(criteria); // => [ 'and', { Collection: 'persons', Property: 'phone', operator: 'eq', operatorValue: '23138213' }, { Collection: 'persondetails', Property: 'country', operator: 'eq', operatorValue: 'Russia' }, [ 'or', { Collection: 'persons', Property: 'city', operator: 'eq', operatorValue: 'york' } ] ]
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

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

暂无
暂无

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

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