【问题标题】:Traversing recursively through an array and modifying values of object properties in JavaScript在 JavaScript 中递归遍历数组并修改对象属性的值
【发布时间】:2020-12-23 23:21:12
【问题描述】:

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

我有 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. 第一项数组(或嵌套数组)总是“与”或“或”
  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

请参阅 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)

我该如何解决这个问题?

【问题讨论】:

    标签: javascript node.js algorithm recursion data-structures


    【解决方案1】:

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

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

    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))

    【讨论】:

    • 我想知道为什么这行得通,但我的解决方案却不行。是不是因为obj.forEach((item,index)=>{} 创建了一个新的itemfor (const item of arr) {} 没有?
    • @SamuraiJack 您需要将Object.entries(item).forEach((item) item 变量。 item[1]=parameters[item[1].split('$p:')[1]] 只是要更新条目数组。它不会更新你要更新的外部item对象
    • 您的逻辑很难遵循,但item[1]=parameters[item[1].split('$p:')[1]] 只是从Object.entries 重新分配第二项,这是数组中的一个简单字符串。这不会改变原始对象——您需要对该对象的引用来更改它的属性。
    • @SamuraiJack 同样,您不需要遍历对象的条目。您事先了解这些属性。 split 也不是必需的,因为每个对象的 Property 键已经包含您要获取的 parameters 的键
    • 谢谢@adiga
    【解决方案2】:

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

    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; }

    【讨论】:

    • 为什么这个解决方案有效而我的无效?
    • 您将对象的值分配给一个数组。通过获取对象的条目,它没有对对象的引用。为了克服这个问题,您需要对象和一个键并为其分配新值。
    • 谢谢妮娜。对我来说,这是一个愚蠢的错误。我标记了阿迪加的答案,因为他是第一个。 :) +1
    • @SamuraiJack 你应该选择最佳答案,而不是第一个答案。
    • @ggorlen 当他们都一样优秀时你会怎么做?
    【解决方案3】:

    一个干净的递归版本:

    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)
          )
    ]
    

    【讨论】:

    • 刚刚看到评论线程解释说值不会总是被替换。这里的第二个版本仍然可以工作。但是,它不处理所寻求的属性不在参数对象中的情况。我假设它需要默认返回原始值,例如:(_, key) => params [key] || obj .operatorValue,或者如果可接受的参数值包括 false-y 值,则需要更复杂的版本。
    【解决方案4】:

    这是使用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>

    免责声明:我是object-scan的作者

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-19
      • 2019-02-07
      • 1970-01-01
      • 2011-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-09
      相关资源
      最近更新 更多