原始字符串文本

JEP

12

作者

道林

状态

认可的

创建

2015年4月9日

摘要

为了提高语言的可用性并简化解析器的实现,JEP建议对JMESPath进行以下修改:

  • 添加 原始字符串文本 允许表达式包含未被JSON转义序列(例如“\n”、“\r”、“\u005C”)改变的原始字符串的JMESPath。

  • 不赞成当前的文本解析行为,该行为允许将未加引号的JSON字符串解析为JSON字符串,从而消除JMESPath语法中的歧义,并有助于确保实现之间的一致性。

此建议旨在为JMESPath添加以下语法:

'foobar'
'foo\'bar'
`bar` -> Parse error/warning (implementation specific)

动机

中提供了原始字符串文本 various programming languages 为了防止特定于语言的解释(即JSON解析)并消除转义的需要,避免了一个称为 leaning toothpick syndrome (LTS) . 倾斜牙签综合症是一个问题,在这种情况下,为了避免分隔符冲突,字符串由于过度使用转义字符而变得不可读(例如。, \\\\\\

在计算JMESPath表达式时,通常需要使用不是从被计算的数据中提取的字符串文字,而是静态地使用编译的JMESPath表达式的一部分。字符串文字在许多领域都很有用,但在调用函数或建立多选列表和散列时最为明显。

以下表达式返回在字符串中找到的字符数 foo . 解析此表达式时, `"foo" 被解析为JSON值,该值生成 ``foo` ::

`"foo"`

下面的表达式在功能上是等价的。注意,JSON文本中省略了引号:

`foo`

这些字符串文本使用JSON解析器进行解析,根据 RFC 4627 ,它将扩展unicode转义序列、换行符和rfc4627第2.5节中记录的其他几个转义序列。

例如,使用转义的unicode值 \u002B 扩展为 + 在以下JMESPath表达式中::

`"foo\u002B"` -> "foo+"

您可以在JSON文本中转义转义序列,以防止转义序列被展开:

`"foo\\u002B"` -> "foo\u002B"
`foo\\u002B` -> "foo\u002B"

虽然这允许您提供文本字符串,但它存在以下问题:

  1. 导致额外的JSON解析惩罚。

  2. 如果您确实希望数据按字面上提供的方式表示(这可能导致LTS),则需要转义转义字符的认知开销。如果要转义的数据要与另一种使用 \ 作为转义符,反斜杠字符的数量将加倍。

  3. 向JMESPath语法引入了一个模糊规则,该规则需要基于散文的规范来解决解析器实现中的歧义。

    目前相关的字面语法规则定义如下:

    literal = "`" json-value "`"
    literal =/ "`" 1*(unescaped-literal / escaped-literal) "`"
    unescaped-literal = %x20-21 /       ; space !
                            %x23-5B /   ; # - [
                            %x5D-5F /   ; ] ^ _
                            %x61-7A     ; a-z
                            %x7C-10FFFF ; |}~ ...
    escaped-literal   = escaped-char / (escape %x60)
    json-value = false / null / true / json-object / json-array /
                 json-number / json-quoted-string
    false = %x66.61.6c.73.65   ; false
    null  = %x6e.75.6c.6c      ; null
    true  = %x74.72.75.65      ; true
    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
    

    这个 literal 模棱两可的规则是因为 unescaped-literal 包括所有相同的字符 json-value 匹配,允许任何有效的JSON值在 unescaped-literaljson-value .

理论基础

在为JMESPath实现解析器时,由于允许JSON字符串文本加上省略引号(例如。, `foo `). JMESPath的这一特定方面不能在上下文无关语法中明确地描述,并且可能成为实现JMESPath解析器时出现错误的常见原因。

解析JSON文本还有其他复杂的问题。以下是当前在JMESPath中解析JSON文本值所需的步骤:

  1. 当A ` 遇到标记,请开始分析JSON文本。

  2. 收集开头之间的每个字符 ` 结束了 ` 令牌,包括任何逃犯 ` 字符(即。, \ )并将字符存储在一个变量中(我们称之为 ``$lexeme`

  3. 复制的内容 $lexeme 删除所有前导空格和尾随空格的临时值。我们称之为 $temp (目前没有记录,但在 JMESPath compliance tests

  4. 如果 $temp 可以解析为有效的JSON,然后将解析的结果用作文本标记的值。

  5. 如果 $temp 无法分析为有效的JSON,然后包装 $lexeme 在双引号中,并将包装的值解析为JSON字符串,使以下表达式等效: `foo ` == `"foo" ‘和 `[1, ] ` == `"[1, ]" '.

可以合理地假设,JMESPath表达式中JSON文本的最常见用例是为函数参数提供字符串值,或者为多选列表或多选哈希中的值提供文本字符串值。为了更容易地提供字符串值,决定JMESPath应该允许省略字符串周围的引号。

这项建议认为,不赞成在解析JSON文本时省略引号,而应该向JMESPath添加一个适当的字符串文本规则。

规范

原始字符串文字是以单引号开头和结尾的值,不解释转义字符,并且可以包含转义单引号以避免分隔符冲突。

实例

以下是几个有效的原始字符串文本及其解析方式的示例:

  • 一个基本的原始字符串文本,解析为 foo bar ::

    'foo bar'
    
  • 转义单引号,解析为 foo'bar ::

    'foo\'bar'
    
  • 包含新字符串的原始字符串:

    'foo
    bar
    baz!'
    

    上面的表达式将被解析为包含新行的字符串::

    foo
    baz
    bar!
    
  • 包含转义字符的原始字符串文本,解析为 foo\nbar ::

    foo\nbar
    

ABNF

将添加以下ABNF语法规则,并且在允许表达式的任何位置都允许:

raw-string        = "'" *raw-string-char "'"
; The first grouping matches any character other than "\"
raw-string-char   = (%x20-26 / %x28-5B / %x5D-10FFFF) / raw-string-escape
raw-string-escape = escape ["'"]

此规则允许原始字符串中的任何字符,包括转义单引号。

除了添加 raw-string 规则,那个 literal ABNF中的规则将更新为:

literal = "`" json-value "`"

影响

对JMESPath现有用户的影响是,删除引号的JSON文本的使用应该转换为使用语法的字符串文本规则。这种转换是否绝对必要,将取决于具体的JMESPath实现。

实现可以选择支持在JSON文本表达式中允许省略引号的旧语法。如果一个实现选择了这种方法,那么该实现应该向用户发出某种警告,让他们知道这是不可取的,以及可能与其他JMESPath实现不兼容的情况。

为了支持JMESPath实现中的这种差异,必须删除所有涉及省略引号的JSON文本符合性测试用例,并且除非放在JEP 12特定的测试套件中,否则不允许在符合性测试中使用无效的未加引号的JSON值的测试用例,允许支持JSON文本中省略引号的实现过滤掉jep12特定的测试用例。

替代方法

有几种可供选择的方法。

保持原样

这是一个合理有效的建议。保持JMESPath不变将避免对语法的破坏性更改,用户可以继续使用多个转义字符来避免分隔符冲突。

此建议的目标不是向JMESPath添加功能,而是使该语言更易于使用、更易于推理和更易于实现。目前,JSON解析的行为是不明确的,在实现JMESPath解析器时需要特殊的大小写。由于这种模糊性,它还允许实现中的细微差异。

举个例子:

`[1`

一个实现可以将此表达式解释为字符串值为的JSON字符串 "[1" ,而其他实现可能会引发解析错误,因为表达式的第一个字符似乎是有效的JSON。

通过更新语法以在JSON文本标记中要求有效的JSON,我们可以完全消除这种歧义,从而从各种JMESPath实现中消除潜在的不一致源。

不允许在原始字符串中使用单引号

此建议指出原始字符串文字中的单引号必须用反斜杠转义。另一种方法是不允许在原始字符串文本中使用单引号。这会简化 raw-string 语法规则会严重限制 raw-string 规则,强制用户使用 literal 规则。

使用可自定义的分隔符

有几种语言允许在原始字符串周围放置自定义分隔符。例如,Lua允许 long bracket 原始字符串用 [[]] 任何数量的平衡 = 括号内的字符:

[==[foo=bar]==] -- parsed as "foo=bar"

这种方法非常灵活,无需转义任何字符;但是,这不能用常规语法表示。解析器需要跟踪打开的分隔符的数量,并确保使用适当数量的匹配字符关闭分隔符。

在这个JEP中描述的字符串文本的添加并不排除以后添加由Lua等语言提供的heredoc或定界符样式的字符串文本, DC++ 等。