【问题标题】:Mapping items in an array映射数组中的项目
【发布时间】:2020-02-17 15:40:57
【问题描述】:

希望这是一个简单的好方法,但我就是不知道该怎么做。

我想用 rego 将数组中的项目映射到更干净的版本。比如下面的数据

data = [
  {
    "some": "value",
    "another": "mvalue",
    "dont": "want"
  },
  {
    "some": "value1",
    "another": "mvalue1",
    "dont": "want1"
  },
  {
    "some": "value2",
    "another": "mvalue2",
    "dont": "want2"
  }
]

我想把数据变成

result = [
  {
    "some": "value",
    "another": "mvalue"
  },
  {
    "some": "value1",
    "another": "mvalue1"
  },
  {
    "some": "value2",
    "another": "mvalue2"
  }
]

我认为最接近的两个是

result1 = cleaned {
    cleaned := {d | 
        d := {
            "some": data[_].some,
            "another": data[_].another
        }
    }
}

result2 = cleaned {
    d := data[_]
    cleaned := {
        "some": p.some,
        "another": p.another
    }
}

【问题讨论】:

    标签: open-policy-agent rego


    【解决方案1】:

    TLDR;如果字段是静态的并且您可以轻松枚举它们,那么您的两个解决方案几乎都是正确的(请参阅下文了解它们不正确的原因。)以下是正确的方法:

    result = [
      mapped |
        original := data[_]
        mapped := {"some": original["some"], "another": original.another}
    ]
    

    一个更优雅的选项是定义要包含或排除的字段,就像@eephillip 的示例中一样。例如:

    result = [
      mapped |
        original := data[_]
        mapped := {k: v |
          some k
          v := original[k]
          not exclude[k]}
    ]
    
    exclude = {"dont", "dont2"}  # defines a SET of keys to exclude
    

    当然,您可以通过让内部理解调用实现其他过滤器的函数来进一步概括它。

    这是一个交互式示例:https://play.openpolicyagent.org/p/P6wPd3rudJ


    关于原始解决方案的两个说明。

    1。 result1 不能正确迭代 data

    {d | 
      d := {
        "some": data[_].some,        # problem: _ is a different variable in each expression
        "another": data[_].another
      }
    }
    

    从概念上讲,_ 的每次出现都是一个唯一 变量。如果显式声明变量,问题就更明显了:

    # note: this is still wrong
    {d | 
      some i, j
      d := {
        "some": data[i]["some"],
        "another": data[j].another
      }
    }
    

    如果你运行它,你会发现它产生了一个交叉产品(这不是你想要的)。您希望从 相同的 对象中选择“一些”和“另一个”字段,如下所示:

    {d | 
      some i
      d := {
        "some": data[i]["some"],
        "another": data[i].another
      }
    }
    

    当然,想出唯一的变量名可能会很痛苦,因此您可以使用_。只是不要将多个_ 变量误认为是指同一个值。我们可以将语句重写为使用_,如下:

    {d | 
      obj := data[_]
      d := {
        "some": obj["some"],
        "another": obj.another
      }
    }
    

    result2 很接近,但可能会分配多个值(应该避免)

    result2 = cleaned {
        d := data[_]
        cleaned := {               # problem: there could be multiple values for 'cleaned'
            "some": d["some"],
            "another": d.another
        }
    }
    

    如果满足BODY 中的语句,则NAME = VALUE { BODY } 形式的规则将VALUE 分配给NAME。如果你省略BODY,即你写NAME = VALUE,那么BODY默认为true(总是满足的)

    在你上面的例子中:

    • NAMEresult2
    • VALUEcleaned
    • BODYd := data[_]; cleaned := {...}

    在 Rego 中,我们将这些规则称为 "complete rules"。完整的规则只是将单个值分配给变量的 IF-THEN 语句。 “IF”部分是规则主体,“THEN”部分是分配。您应该避免编写可能将多个值分配给同一变量的规则,因为这可能会导致评估时间错误。例如:

    # do not do this
    result = v { 
       v := data[_]   # if 'data' is [1,2,3] then what is the value of 'result'? Hint: There is more than one answer.
    }
    

    如果你想为一个变量分配多个值,那么你可以定义一个"partial rule" 例如:

    result[v] {       # result is a SET.
       v := data[_]
    }
    

    【讨论】:

    • 太棒了。感谢您的解释。我曾使用命名值尝试过结果 1,但没有帮助,但设置中间值(你的 original 是关键)。干杯。
    • 优雅的选项和@eephillips 答案也适用于对象中的键是可选的。
    【解决方案2】:

    在理解过程中拒绝键名怎么样? 可能是一种更优雅的方式来执行此操作,但可能会有所帮助。

    package play
    
    reject(key) = result {
        remove := [ "dont", "someotherthing" ]
        result :=  key == remove[_]
    }
    
    result = d {
        d := [ obj | 
               val := input.data[_]; 
               obj := { k: v | 
                        v := val[k] 
                        not reject(k)
                      }
              ] 
     }
    

    https://play.openpolicyagent.org/p/1A3DNLiNfj

    【讨论】:

    • 感谢您的回答。如果您想删除特定的键,这很好。
    猜你喜欢
    • 2020-05-25
    • 2022-11-08
    • 1970-01-01
    • 2020-01-31
    • 1970-01-01
    • 2016-05-04
    • 2022-01-15
    • 2018-08-22
    • 2018-11-16
    相关资源
    最近更新 更多