筛选表达式

JEP

7

作者

詹姆斯·萨耶维尼

状态

认可的

创建

2013年12月16日

摘要

这个JEP建议对JMESPath进行语法修改,以允许使用过滤器表达式。过滤表达式允许基于匹配表达式选择列表元素。还引入了一个文本表达式(来自jep3),这样就可以根据文本值匹配元素。

动机

查询JSON对象时的一个常见请求是能够基于特定值选择元素。例如,给定一个JSON对象:

{"foo": [{"state": "WA", "value": 1},
         {"state": "WA", "value": 2},
         {"state": "CA", "value": 3},
         {"state": "CA", "value": 4}]}

用户可能希望选择 foo 具有 state 关键 WA . 目前在JMESPath中无法做到这一点。这个JEP将引入一种语法,它允许:

foo[?state == `WA`]

此外,用户可能希望将其他表达式投影到与筛选器表达式匹配的值上。例如,给定上面的数据,选择 value 所有具有 state 属于 WA ::

foo[?state == `WA`].value

会回来 [1, 2] .

规范

筛选器表达式的更新语法::

bracket-specifier      = "[" (number / "*") "]" / "[]"
bracket-specifier      =/ "[?" list-filter-expression "]"
list-filter-expression = expression comparator expression
comparator             = "<" / "<=" / "==" / ">=" / ">" / "!="
expression             =/ literal
literal                = "`" json-value "`"
literal                =/ "`" 1*(unescaped-literal / escaped-literal) "`"
unescaped-literal      = %x20-21 /       ; space !
                            %x23-5A /   ; # - [
                            %x5D-5F /   ; ] ^ _
                            %x61-7A     ; a-z
                            %x7C-10FFFF ; |}~ ...
escaped-literal        = escaped-char / (escape %x60)

这个 json-value rule是任何有效的json值。虽然建议实现使用现有的JSON解析器来解析 json-value ,为了完整起见,下面添加了语法:

json-value = "false" / "null" / "true" / json-object / json-array /
             json-number / json-quoted-string
json-quoted-string = %x22 1*(unescaped-literal / escaped-literal) %x22
begin-array     = ws %x5B ws  ; [ left square bracket
begin-object    = ws %x7B ws  ; { left curly bracket
end-array       = ws %x5D ws  ; ] right square bracket
end-object      = ws %x7D ws  ; } right curly bracket
name-separator  = ws %x3A ws  ; : colon
value-separator = ws %x2C ws  ; , comma
ws              = *(%x20 /              ; Space
                    %x09 /              ; Horizontal tab
                    %x0A /              ; Line feed or New line
                    %x0D                ; Carriage return
                   )
json-object = begin-object [ member *( value-separator member ) ] end-object
member = quoted-string name-separator json-value
json-array = begin-array [ json-value *( value-separator json-value ) ] end-array
json-number = [ minus ] int [ frac ] [ exp ]
decimal-point = %x2E       ; .
digit1-9 = %x31-39         ; 1-9
e = %x65 / %x45            ; e E
exp = e [ minus / plus ] 1*DIGIT
frac = decimal-point 1*DIGIT
int = zero / ( digit1-9 *DIGIT )
minus = %x2D               ; -
plus = %x2B                ; +
zero = %x30                ; 0

比较运算符

支持以下操作:

  • == ,相等性测试。

  • != ,不平等测试。

  • < ,小于。

  • <= ,小于或等于。

  • > ,大于。

  • >= ,大于或等于。

每个操作的行为取决于每个计算表达式的类型。

下面根据对应的JSON类型定义每个运算符的比较语义:

相等运算符

为了 string/number/true/false/null 类型,相等是完全匹配的。A string 等于另一个 string 如果他们有准确的代码点序列。文字值 true/false/null 它们的值只等于它们自己的值。如果两个JSON对象具有相同的键集,则它们是相等的(对于第一个JSON对象中的每个键,第二个JSON对象中都存在一个值相等的键)。如果两个JSON数组的顺序相同,则它们的顺序相同 xy ,每个 i 在里面 xx[i] == y[i]

排序运算符

排序运算符 >, >=, <, <=only 对数字有效。使用比较运算符计算任何其他类型都将生成 null 值,这将导致元素从结果列表中排除。例如,给定:

search('foo[?a<b]', {"foo": [{"a": "char", "b": "char"},
                             {"a": 2, "b": 1},
                             {"a": 1, "b": 2}]})

对foo列表中的三个元素进行计算 a < b . 第一个元素解析为比较 "char" < "bar" ,并且由于这些类型是字符串,因此表达式将导致 null ,因此第一个元素不包含在结果列表中。第二个元素解析为 2 < 1 ,这就是 false ,因此第二个元素从结果列表中排除。第三个表达式解析为 1 < 2 评估结果 true ,因此第三个元素包含在列表中。这个表达式的最终结果是 [{{"a": 1, "b": 2}}] .

过滤语义

当筛选表达式匹配时,匹配的元素将包含在筛选的响应中。

使用前面的示例,给定以下数据:

{"foo": [{"state": "WA", "value": 1},
         {"state": "WA", "value": 2},
         {"state": "CA", "value": 3},
         {"state": "CA", "value": 4}]}

表达式 foo[?state == `WA`] 将返回以下值:

[{"state": "WA", "value": 1}]

文字表达式

在JEP中还添加了文本表达式,它本质上是一个由“`”字符包围的JSON值。您可以通过“\`”来转义“`”字符,如果JSON值中出现“`”字符,则也必须对其进行转义。lexer中的一个简单的两次传递算法可以先处理任何转义的“`”字符,然后再将结果字符串交给JSON解析器。

因为到目前为止,字符串文字是最常见的JSON值类型,因此支持一种替代语法,其中字符串不需要开始和结束双引号。例如::

`foobar`   -> "foobar"
`"foobar"` -> "foobar"
`123`      -> 123
`"123"`    -> "123"
`123.foo`  -> "123.foo"
`true`     -> true
`"true"`   -> "true"
`truee`    -> "truee"

子表达式的右侧不允许使用文字表达式::

foo[*].`literal`

但它们可以放在左手边:

`{"foo": "bar"}`.foo

它们也可以包含在过滤器表达式之外的其他表达式中。例如::

{value: foo.bar, type: `multi-select-hash`}

理论基础

所提出的过滤器表达式语法被选择为有足够的表达能力,以满足可能需要执行的任何类型的过滤器,同时尽可能地最小化。为了帮助说明这一点,下面是一些考虑过的替代语法。

在最简单的情况下,一个可能的过滤器语法是::

foo[bar == baz]

或者概括地说: [identifier comparator literal-value] . 然而,这有几个问题:

  • 不可能基于两个表达式进行筛选(获取 foo 键等于它的 bar 关键。

  • 文字值位于右侧,因此如果标识符和文字值交换,则很难进行故障排除: foo[baz == bar] .

  • 如果没有一些识别标记,一元过滤器将不可能,因为它们将是模棱两可的。表达式是 [foo] 用一个带有真值的foo键过滤所有元素,或者它是一个选择 foo 每个散列的密钥?使用标记启动筛选器表达式,例如 [? 请明确这是一个过滤器表达式。

  • 这使得针对文本JSON数组和对象进行过滤的语法很难直观地进行分析。”过滤所有 foo key是一个具有单个整数值2的列表: [foo == [2]] .

  • 添加文本表达式使它们即使在筛选器表达式之外也很有用。例如,在 multi-select-hash ,可以创建任意键值对: {{a: foo.bar, b: `some string`}} .

这个JEP是有目的地最小化的。将来可以添加几个扩展:

  • 支持 [? ... ] . 这将启用过滤器中的或表达式等构造。这将允许一元表达式。

为了使其有用,我们需要定义与真值和假值相对应的值,例如空列表是假值。另外,“or expressions”需要根据表达式的真/假值(而不是表达式是否求值为null)更改其语义以进行分支。

这无疑是未来的发展方向,在过滤器中添加任意表达式将是向后兼容的更改,因此它不是JEP的一部分。

  • 允许筛选器表达式作为顶级表达式。这很可能会卷土重来 true/false 任何匹配的值。

如果您可以将其与可以接受列表作为筛选其他元素的掩码的内容结合起来,这可能会很有用。