我正在寻找允许我验证json的正则表达式。
我对Regex非常陌生,我知道使用Regex进行解析很不好,但是可以用来验证吗?
我正在寻找允许我验证json的正则表达式。
我对Regex非常陌生,我知道使用Regex进行解析很不好,但是可以用来验证吗?
Answers:
大多数现代正则表达式实现都允许递归正则表达式,该正则表达式可以验证完整的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第6节中指定的最小一致性检查。但是,它仅用作安全性测试和基本的非有效性预防措施:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
匹配,而顶级JSON值必须是数组或对象。在字符串或空格中允许的字符集方面也存在许多问题。
是的,常见的误解是正则表达式只能匹配正则语言。实际上,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(如果已通过验证),那么这是最好的方法。
\d
是危险的。在许多正则表达式实现中,\d
匹配的数字的Unicode定义不仅[0-9]
包括而是包含备用脚本。
\d
与PHP的PCRE实现中的unicode号不匹配。例如٩
符号(0x669阿拉伯-印度文数字9)将使用图案匹配#\p{Nd}#u
而不是#\d#u
/u
标志。JSON以UTF-8编码。对于适当的正则表达式,您应该使用该标志。
由于JSON(嵌套{...}
-s)的递归特性,正则表达式不适合对其进行验证。当然,某些正则表达式类型可以递归地匹配模式*(并因此可以匹配JSON),但是生成的模式看起来很恐怖,绝不能在生产代码IMO中使用!
*但是请注意,许多正则表达式实现不支持递归模式。在流行的编程语言中,这些支持递归模式:Perl,.NET,PHP和Ruby 1.9.2。
我尝试了@mario的答案,但是它对我不起作用,因为我已经从JSON.org(archive)下载了测试套件,并且有4个失败的测试(fail1.json,fail18.json,fail25.json,fail27。 json)。
我调查了错误并发现了它fail1.json
实际上是正确的(根据手册的注释,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的所有合法测试。
查看JSON文档,如果目标只是检查适应性,则似乎正则表达式可以简单地分为三个部分:
[]
{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...全部一起:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
如果JSON字符串包含newline
字符,则应使用singleline
regex样式上的开关使其.
匹配newline
。请注意,这不会在所有错误的JSON上失败,但是如果基本JSON结构无效,这将失败,这是在将其传递给解析器之前进行基本健全性验证的直接方法。
我创建了一个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
如上文所述,如果您使用的语言带有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)
如果我错过了一些无意间破坏了这一点的事情,我将不胜感激!
它会验证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)*\}$
{
"key":"string",
"key": 56,
"key":{
"attr":"integer",
"attr": 12
},
"key":{
"key":[
{
"attr": 4,
"attr": "string"
}
]
}
}