代码之家  ›  专栏  ›  技术社区  ›  Shard Brian Ellis

Regex验证JSON

  •  76
  • Shard Brian Ellis  · 技术社区  · 16 年前

    我正在寻找一个允许我验证JSON的regex。

    我对regex非常陌生,我很清楚使用regex进行解析是不好的,但是可以用它来验证吗?

    11 回复  |  直到 8 年前
        1
  •  164
  •   Gajus    8 年前

    是的,可以进行完整的regex验证。

    大多数现代的regex实现都允许递归regexpressions,它可以验证完整的JSON序列化结构。这个 json.org specification 非常简单。

    $pcre_regex = '
      /
      (?(DEFINE)
         (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )    
         (?<boolean>   true | false | null )
         (?<string>    " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
         (?<array>     \[  (?:  (?&json)  (?: , (?&json)  )*  )?  \s* \] )
         (?<pair>      \s* (?&string) \s* : (?&json)  )
         (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \s* \} )
         (?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
      )
      \A (?&json) \Z
      /six   
    ';
    

    它在PHP中与 PCRE functions . 应该在Perl中不做任何修改;当然可以适应其他语言。它也成功了 JSON test cases

    更简单的RFC4627验证

    更简单的方法是最小一致性检查,如 RFC4627, section 6 . 但是,它只是作为安全测试和基本的非有效性预防措施:

      var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
             text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
         eval('(' + text + ')');
    
        2
  •  26
  •   Hrant Khachatrian    15 年前

    是的,普遍的误解是正则表达式只能匹配 regular languages . 事实上, PCRE函数可以比常规语言更匹配 ,它们甚至可以匹配一些非上下文无关的语言! Wikipedia's article on RegExps 有一个特别的部分。

    JSON可以通过多种方式使用PCRE识别! @Mario展示了一个使用命名子模式和 back-references . 然后他指出,应该有一个解决方案使用 recursive patterns (?R) . 下面是用PHP编写的此类regexp的示例:

    $regexString = '"([^"\\\\]*|\\\\["\\\\bfnrt\/]|\\\\u[0-9a-f]{4})*"';
    $regexNumber = '-?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?';
    $regexBoolean= 'true|false|null'; // these are actually copied from Mario's answer
    $regex = '/\A('.$regexString.'|'.$regexNumber.'|'.$regexBoolean.'|';    //string, number, boolean
    $regex.= '\[(?:(?1)(?:,(?1))*)?\s*\]|'; //arrays
    $regex.= '\{(?:\s*'.$regexString.'\s*:(?1)(?:,\s*'.$regexString.'\s*:(?1))*)?\s*\}';    //objects
    $regex.= ')\Z/is';
    

    我在用 (?1) 而不是 ?R) 因为后者引用了 整个的 模式,但我们有 \A \Z 不应在子模式中使用的序列。 (?1) 对由最外面的括号标记的regexp的引用(这就是最外面的 ( ) 不是从 ?: )因此,regexp的长度变为268个字符:)

    /\A("([^"\\]*|\\["\\bfnrt\/]|\\u[0-9a-f]{4})*"|-?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?|true|false|null|\[(?:(?1)(?:,(?1))*)?\s*\]|\{(?:\s*"([^"\\]*|\\["\\bfnrt\/]|\\u[0-9a-f]{4})*"\s*:(?1)(?:,\s*"([^"\\]*|\\["\\bfnrt\/]|\\u[0-9a-f]{4})*"\s*:(?1))*)?\s*\})\Z/is
    

    无论如何,这应该被视为“技术示范”,而不是实际的解决方案。在PHP中,我将通过调用 json_decode() 功能(如@epcylon所述)。如果我要去 使用 那个JSON(如果它经过验证),那么这是最好的方法。

        3
  •  13
  •   Bart Kiers    11 年前

    因为JSON的递归性质(嵌套 {...} -s),regex不适合验证它。当然,有些regex口味可以递归地匹配模式 * (因此可以与JSON相匹配),但是结果模式很可怕,而且永远不应该在生产代码IMO中使用!

    * 但是要小心,很多regex实现都是这样的 支持递归模式。在流行的编程语言中,这些语言支持递归模式:perl、.net、php和ruby 1.9.2

        4
  •  7
  •   Gino Pane    10 年前

    我试过@Mario的答案,但它对我不起作用,因为我已经从json.org下载了测试套件。( archive )测试失败4次(fail1.json,fail18.json,fail25.json,fail27.json)。

    我已经调查了错误并发现 fail1.json 实际上是正确的(根据手册 note RFC-7159 有效字符串也是有效的JSON)。文件 fail18.json 也不是这样,因为它实际上包含正确的深度嵌套JSON:

    [[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
    

    所以剩下两个文件: fail25.json fail27.json :

    ["  tab character   in  string  "]
    

    ["line
    break"]
    

    两者都包含无效字符。所以我像这样更新了模式(更新了字符串子模式):

    $pcreRegex = '/
              (?(DEFINE)
                 (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
                 (?<boolean>   true | false | null )
                 (?<string>    " ([^"\n\r\t\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " )
                 (?<array>     \[  (?:  (?&json)  (?: , (?&json)  )*  )?  \s* \] )
                 (?<pair>      \s* (?&string) \s* : (?&json)  )
                 (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*  )?  \s* \} )
                 (?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* )
              )
              \A (?&json) \Z
              /six';
    

    所以现在所有的法律测试 json.org 可以通过。

        5
  •  3
  •   Gajus    11 年前

    我创建了一个Mario解决方案的Ruby实现,它可以工作:

    # encoding: utf-8
    
    module Constants
      JSON_VALIDATOR_RE = /(
             # define subtypes and build up the json syntax, BNF-grammar-style
             # The {0} is a hack to simply define them as named groups here but not match on them yet
             # I added some atomic grouping to prevent catastrophic backtracking on invalid inputs
             (?<number>  -?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?){0}
             (?<boolean> true | false | null ){0}
             (?<string>  " (?>[^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ){0}
             (?<array>   \[ (?> \g<json> (?: , \g<json> )* )? \s* \] ){0}
             (?<pair>    \s* \g<string> \s* : \g<json> ){0}
             (?<object>  \{ (?> \g<pair> (?: , \g<pair> )* )? \s* \} ){0}
             (?<json>    \s* (?> \g<number> | \g<boolean> | \g<string> | \g<array> | \g<object> ) \s* ){0}
           )
        \A \g<json> \Z
        /uix
    end
    
    ########## inline test running
    if __FILE__==$PROGRAM_NAME
    
      # support
      class String
        def unindent
          gsub(/^#{scan(/^(?!\n)\s*/).min_by{|l|l.length}}/u, "")
        end
      end
    
      require 'test/unit' unless defined? Test::Unit
      class JsonValidationTest < Test::Unit::TestCase
        include Constants
    
        def setup
    
        end
    
        def test_json_validator_simple_string
          assert_not_nil %s[ {"somedata": 5 }].match(JSON_VALIDATOR_RE)
        end
    
        def test_json_validator_deep_string
          long_json = <<-JSON.unindent
          {
              "glossary": {
                  "title": "example glossary",
              "GlossDiv": {
                      "id": 1918723,
                      "boolean": true,
                      "title": "S",
                "GlossList": {
                          "GlossEntry": {
                              "ID": "SGML",
                    "SortAs": "SGML",
                    "GlossTerm": "Standard Generalized Markup Language",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986",
                    "GlossDef": {
                                  "para": "A meta-markup language, used to create markup languages such as DocBook.",
                      "GlossSeeAlso": ["GML", "XML"]
                              },
                    "GlossSee": "markup"
                          }
                      }
                  }
              }
          }
          JSON
    
          assert_not_nil long_json.match(JSON_VALIDATOR_RE)
        end
    
      end
    end
    
        6
  •  1
  •   Andrew Marshall    12 年前

    对于“字符串和数字”,我认为数字的部分正则表达式:

    -?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
    

    应改为:

    -?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
    

    因为数字的小数部分是可选的,而且从 - 中的符号 [+-] 因为它在括号之间有特殊的含义

        7
  •  1
  •   user117529    10 年前

    JSON数组中的尾随逗号导致了我的Perl5.16挂起,可能是因为它保持了回溯。我不得不添加一个回溯终止指令:

    (?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
                                                                                       ^^^^^^^^
    

    这样,一旦它标识了一个不是“可选”的构造( * ? )它不应该试图对其进行回溯,试图将其识别为其他内容。

        8
  •  1
  •   cjbarth    9 年前

    查看文档 JSON 如果目标仅仅是检查身体状况,那么regex似乎可以简单地分为三个部分:

    1. 字符串开始 以两者之一结尾 [] {}
      • [{\[]{1} [}\]]{1}
      1. 字符是允许的JSON控制字符(仅一个)
        • [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
      2. 包含在 ""
        • ".*?"

    总之: [{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}

    如果json字符串包含 newline 字符,则应使用 singleline 打开你的regex口味,这样 . 比赛 换行符 . 请注意,这不会在所有坏的JSON上都失败,但如果基本JSON结构无效,则会失败,这是在将其传递给解析器之前进行基本健全性验证的直接方法。

        9
  •  0
  •   exside    11 年前

    如上所述,如果您使用的语言附带了一个JSON库,请使用它尝试对字符串进行解码,并在失败时捕获异常/错误!如果语言没有(只是使用freemaker的情况),那么下面的regex至少可以提供一些非常基本的验证(它是为php/pcre编写的,以供更多用户测试/使用)。这并不像公认的解决方案那么简单,但也不是那么可怕。

    ~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
    

    简短说明:

    // we have two possibilities in case the string is JSON
    // 1. the string passed is "just" a JSON object, e.g. {"item": [], "anotheritem": "content"}
    // this can be matched by the following regex which makes sure there is at least a {" at the
    // beginning of the string and a } at the end of the string, whatever is inbetween is not checked!
    
    ^\{\s*\".*\}$
    
    // OR (character "|" in the regex pattern)
    // 2. the string passed is a JSON array, e.g. [{"item": "value"}, {"item": "value"}]
    // which would be matched by the second part of the pattern above
    
    ^\[\n?\{\s*\".*\}\n?\]$
    
    // the s modifier is used to make "." also match newline characters (can happen in prettyfied JSON)
    

    如果我错过了一些会无意中打破这一点的东西,我很感谢您的评论!

        10
  •  -1
  •   Sergey Kamardin    13 年前

    这里是验证字符串的regexp:

    ^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
    

    写的是美国 original syntax diagramm .

        11
  •  -4
  •   Jamie    10 年前

    我知道这是6年前的事了。然而,我认为有一个解决方案,这里没有人提到,这比重新存在要容易得多。

    function isAJSON(string) {
        try {
            JSON.parse(string)  
        } catch(e) {
            if(e instanceof SyntaxError) return false;
        };  
        return true;
    }