正则表达式以验证JSON


89

我正在寻找允许我验证json的正则表达式。

我对Regex非常陌生,我知道使用Regex进行解析很不好,但是可以用来验证吗?


31
为什么要麻烦一个单独的验证步骤?大多数语言都有可以解析JSON的JSON库,如果可以解析,则是有效的。如果没有,图书馆会告诉您。
Epcylon

您需要分析文本才能对其进行验证...

3
@mario-我不知道...我全都在滥用正则表达式,并且非常同情您对“正则表达式必须匹配常规”谬论的异议-但不涉及与工作相关的实际问题。最好的答案实际上是Epcylon的评论……(也许此讨论属于聊天内容?)
Kobi

1
另一个实际用例是在较大的字符串中查找 JSON表达式。如果您只是想问“此字符串是否为JSON对象”,那么可以,JSON解析库可能是一个更好的工具。但是它无法为您在更大的结构中找到JSON对象。
Mark Amery 2014年

1
这不是答案,但是您可以使用Crockford的JSON-js库的这一部分。它使用4个正则表达式并将它们巧妙地组合在一起。
imgx64

Answers:


182

是的,可以进行完整的正则表达式验证。

大多数现代正则表达式实现都允许递归正则表达式,该正则表达式可以验证完整的JSON序列化结构。该json.org规范使得它非常简单。

$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   
';

它具有PCRE功能,在PHP中效果很好。应该在Perl中保持不变;并且可以肯定地适用于其他语言。JSON测试用例也成功。

更简单的RFC4627验证

一种更简单的方法是RFC4627第6节中指定的最小一致性检查。但是,它仅用作安全性测试和基本的非有效性预防措施:

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

22
+1世界上有很多不好的事情,就是那些没有获得正则表达式语法和滥用的人,这是憎恨他们的原因:(
NikiC 2011年

8
@mario,不确定您是否认为我在the-naysayers-部门,但我不是。请注意,您的陈述“大多数现代正则表达式实现都允许递归正则表达式”是值得商de的。AFAIK,仅Perl,PHP和.NET可以定义递归模式。我不会称其为“最”。
巴特·基尔斯

3
@Bart:是的,这是值得商bat的。最具有讽刺意味的是,Javascript正则表达式引擎无法使用此类递归正则表达式来验证JSON(或仅使用精心设计的变通办法)。因此,如果regex == posix regex,则不是一种选择。然而,有趣的是,它可以与现代实现一起使用。即使只有很少的实际用例。(但是,确实,libpcre并不是到处都流行的引擎。)-出于记录:我希望获得合成的逆转徽章,但是您获得的一些小众支持阻碍了这一点。:/
mario

4
不。我追求的是民粹主义徽章,为此我需要20票,但您的答案仍然是10票。因此,恰恰相反,对您的问题的否决对我不利。
mario

2
好吧,进一步看,这个正则表达式还有很多其他问题。它匹配JSON数据,但也匹配一些非JSON数据。例如,单个文字false匹配,而顶级JSON值必须是数组或对象。在字符串或空格中允许的字符集方面也存在许多问题。
dolmen 2013年

31

是的,常见的误解是正则表达式只能匹配正则语言。实际上,PCRE功能比常规语言可以匹配的更多,甚至可以匹配某些非上下文无关的语言!Wikipedia在RegExps的文章有一个特别的部分。

可以通过多种方式使用PCRE识别JSON!@mario显示了一种使用命名子模式和反向引用的绝佳解决方案。然后他指出应该有一种使用递归模式 的解决方案(?R)。这是用PHP编写的此类正则表达式的示例:

$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变为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()函数来验证JSON字符串(就像@Epcylon所述)。如果我要使用该JSON(如果已通过验证),那么这是最好的方法。


1
使用\d是危险的。在许多正则表达式实现中,\d匹配的数字的Unicode定义不仅[0-9]包括而是包含备用脚本。
2013年

@dolmen:您可能是正确的,但您不应该自己编辑该问题。仅将其添加为评论就足够了。
丹尼斯·哈布林克2013年

我认为\d与PHP的PCRE实现中的unicode号不匹配。例如٩符号(0x669阿拉伯-印度文数字9)将使用图案匹配#\p{Nd}#u而不是#\d#u
Hrant Khachatrian

@ hrant-khachatrian:不是因为您没有使用该/u标志。JSON以UTF-8编码。对于适当的正则表达式,您应该使用该标志。
dolmen 2013年

1
@dolmen我确实使用过u修饰符,请再次查看我先前的注释中的模式:)字符串,数字和布尔值在顶层正确匹配。您可以在quanetic.com/Regex上粘贴长的regexp 并尝试一下-Hrant
Khachatrian

14

由于JSON(嵌套{...}-s)的递归特性,正则表达式不适合对其进行验证。当然,某些正则表达式类型可以递归地匹配模式*(并因此可以匹配JSON),但是生成的模式看起来很恐怖,绝不能在生产代码IMO中使用!

*但是请注意,许多正则表达式实现支持递归模式。在流行的编程语言中,这些支持递归模式:Perl,.NET,PHP和Ruby 1.9.2。



16
@all down选民:“正则表达式不适合对其进行验证”并不意味着某些正则表达式引擎无法做到(至少,这就是我的意思)。当然,某些正则表达式实现可以,但是任何有主见的人都只会使用JSON解析器。就像有人问如何仅用一把锤子建造一所完整的房屋一样,我会回答说锤子不适合这项工作,您需要一整套工具和机械。当然,有足够耐力的人可以只用锤子就能做到。
巴特·基尔斯

1
这可能是有效的警告,但不能回答问题。正则表达式可能不是正确的工具,但有些人别无选择。我们被锁定在一个供应商产品中,该产品评估服务的输出以检查其运行状况,并且该供应商提供的用于自定义运行状况检查的唯一选择是一个接受正则表达式的Web表单。评估服务状态的供应商产品不在我团队的控制之下。对我们来说,现在需要使用正则表达式评估JSON,因此,回答“不合适”是不可行的。(我仍然没有对你
投反对票

11

我尝试了@mario的答案,但是它对我不起作用,因为我已经从JSON.org(archive)下载了测试套件,并且有4个失败的测试(fail1.json,fail18.json,fail25.json,fail27。 json)。

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

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

剩下两个文件:fail25.jsonfail27.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的所有合法测试。


这也将仅匹配JSON值(字符串,布尔值和数字),而不是JSON对象/数组。
kowsikbabu

4

查看JSON文档,如果目标只是检查适应性,则似乎正则表达式可以简单地分为三个部分:

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

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

如果JSON字符串包含newline字符,则应使用singlelineregex样式上的开关使其.匹配newline。请注意,这不会在所有错误的JSON上失败,但是如果基本JSON结构无效,这将失败,这是在将其传递给解析器之前进行基本健全性验证的直接方法。


1
建议的正则表达式在某些测试用例上具有可怕的回溯行为。如果您尝试在不完整的json'{“ a”:false,“ b”:true,“ c”:100,“'上运行,它将暂停。例如:regex101.com/r/Zzc6sz。 :[{[] {1}([,:{} [] 0-9。\-+ Eaeflnr-u \ n \ r \ t] |“。*?”)+ [}]] {1}
Toonijn

@Toonijn我已经更新以反映您的评论。谢谢!
cjbarth

3

我创建了一个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

使用\ d很危险。在许多正则表达式实现中,\ d与数字的Unicode定义匹配,该数字不仅是[0-9],还包括备用脚本。因此,除非Ruby中的Unicode支持仍然无效,否则您必须在代码中修复regexp。
dolmen 2013年

据我所知,Ruby使用PCRE,其中\ d与“ digit”的所有unicode定义都不匹配。还是您说应该这样做?
pmarreck

除了它没有。误报:“ \ x00”,[True]。假否定:“ \ u0000”,“ \ n”。挂在:“ [[{””:[{””:[{”“:”(重复1000x)。
2013年

不太难添加为测试用例,然后调整代码以使其通过。但是,如何使它不向深度超过1000的深度吹栈是完全不同的问题……
pmarreck

1

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

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

应该改为:

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

因为数字的小数部分是可选的,所以也可以更安全地转义该-符号,[+-]因为它在方括号之间具有特殊含义


使用\d是危险的。在许多正则表达式实现中,\d匹配的数字的Unicode定义不仅[0-9]包括而是包含备用脚本。
dolmen

看起来有点奇怪,-0是有效数字,但是RFC 4627允许它,并且您的正则表达式符合它。
2013年

1

JSON数组中的结尾逗号导致我的Perl 5.16挂起,可能是因为它一直在回溯。我必须添加一个回溯终止指令:

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

这样,一旦它识别出不是“可选”(*?)的构造,就不应尝试在其上回溯以尝试将其识别为其他东西。


0

如上文所述,如果您使用的语言带有JSON库,请使用它尝试解码字符串,并在失败时捕获异常/错误!如果该语言不行(FreeMarker就是这种情况),则以下正则表达式至少可以提供一些非常基本的验证(它是为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)

如果我错过了一些无意间破坏了这一点的事情,我将不胜感激!


0

验证简单JSON而不是JSONArray的正则表达式

它会验证key(string):value(string,integer,[{{key:value},{key:value}],{key:value})

^\{(\s|\n\s)*(("\w*"):(\s)*("\w*"|\d*|(\{(\s|\n\s)*(("\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))((,(\s|\n\s)*"\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))*(\s|\n\s)*\}){1}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d*|(\{(\s|\n\s)*(("\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))((,(\s|\n\s)*"\w*"):(\s)*("\w*(,\w+)*"|\d{1,}|\[(\s|\n\s)*(\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):(\s)*("\w*"|\d{1,}))*(\s|\n)*\})){1}(\s|\n\s)*(,(\s|\n\s)*\{(\s|\n\s)*(("\w*"):(\s)*(("\w*"|\d{1,}))((,(\s|\n\s)*"\w*"):("\w*"|\d{1,}))*(\s|\n)*\})?)*(\s|\n\s)*\]))*(\s|\n\s)*\}){1}))*(\s|\n)*\}$

通过此JSON验证的示例数据

{
"key":"string",
"key": 56,
"key":{
        "attr":"integer",
        "attr": 12
        },
"key":{
        "key":[
            {
                "attr": 4,
                "attr": "string"
            }
        ]
     }
}


-3

我意识到这是6年前的事了。但是,我认为有一个解决方案,这里没有人提到,这比正则表达式更容易

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.