Tôi đang tìm kiếm một Regex cho phép tôi xác thực json.
Tôi rất mới sử dụng Regex và tôi biết đủ rằng phân tích cú pháp với Regex là không tốt nhưng nó có thể được sử dụng để xác thực không?
Tôi đang tìm kiếm một Regex cho phép tôi xác thực json.
Tôi rất mới sử dụng Regex và tôi biết đủ rằng phân tích cú pháp với Regex là không tốt nhưng nó có thể được sử dụng để xác thực không?
Câu trả lời:
Hầu hết các triển khai regex hiện đại đều cho phép áp dụng lại đệ quy, có thể xác minh cấu trúc tuần tự hóa JSON hoàn chỉnh. Đặc tả json.org làm cho nó khá đơn giản.
$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
';
Nó hoạt động khá tốt trong PHP với các hàm PCRE . Nên hoạt động không bị sửa đổi trong Perl; và chắc chắn có thể được điều chỉnh cho các ngôn ngữ khác. Nó cũng thành công với các trường hợp thử nghiệm JSON .
Một cách tiếp cận đơn giản hơn là kiểm tra tính nhất quán tối thiểu như được quy định trong RFC4627, phần 6 . Tuy nhiên, nó chỉ nhằm mục đích kiểm tra bảo mật và phòng ngừa không hợp lệ cơ bản:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
phù hợp trong khi giá trị JSON cấp cao nhất phải là một mảng hoặc một đối tượng. Nó cũng có nhiều vấn đề trong bộ ký tự được phép trong chuỗi hoặc trong khoảng trắng.
Có, đó là một quan niệm sai lầm phổ biến rằng Biểu thức chính quy chỉ có thể khớp với các ngôn ngữ thông thường . Trên thực tế, các hàm PCRE có thể khớp nhiều hơn các ngôn ngữ thông thường , chúng có thể khớp với cả một số ngôn ngữ không có ngữ cảnh! Bài viết của Wikipedia trên RegExps có một phần đặc biệt về nó.
JSON có thể được nhận dạng bằng PCRE theo một số cách! @mario đã chỉ ra một giải pháp tuyệt vời bằng cách sử dụng các tệp con được đặt tên và tham chiếu ngược . Sau đó, ông lưu ý rằng cần có một giải pháp sử dụng các mẫu đệ quy (?R)
. Đây là một ví dụ về regexp được viết bằng 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';
Tôi đang sử dụng (?1)
thay (?R)
vì vì cái sau tham chiếu đến toàn bộ mẫu, nhưng chúng tôi có \A
và \Z
các chuỗi không nên được sử dụng bên trong các phân tử. (?1)
tham chiếu đến regexp được đánh dấu bằng dấu ngoặc đơn ngoài cùng (đây là lý do tại sao ngoài cùng ( )
không bắt đầu bằng ?:
). Vì vậy, RegExp dài 268 ký tự :)
/\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
Dù sao, đây cũng nên được coi như một "cuộc trình diễn công nghệ", không phải là một giải pháp thực tế. Trong PHP, tôi sẽ xác thực chuỗi JSON bằng cách gọi json_decode()
hàm (giống như @Epcylon đã lưu ý). Nếu tôi định sử dụng JSON đó (nếu nó đã được xác thực), thì đây là phương pháp tốt nhất.
\d
rất nguy hiểm. Trong nhiều triển khai regexp \d
khớp với định nghĩa Unicode của một chữ số không chỉ [0-9]
mà thay vào đó bao gồm các tập lệnh thay thế.
\d
không khớp với số unicode trong việc triển khai PCRE của PHP. Ví dụ ٩
biểu tượng (0x669 arabic-Ấn Độ chữ số chín) sẽ được xuất hiện bằng mô hình #\p{Nd}#u
nhưng không#\d#u
/u
cờ. JSON được mã hóa bằng UTF-8. Để có một regexp thích hợp, bạn nên sử dụng cờ đó.
u
sửa đổi, vui lòng xem lại các mẫu trong nhận xét trước của tôi :) Các chuỗi, số và boolean ĐƯỢC đối sánh chính xác ở cấp cao nhất. Bạn có thể dán regexp dài đây quanetic.com/Regex và cố gắng bản thân
Do bản chất đệ quy của JSON ( {...}
-s lồng nhau ), regex không phù hợp để xác thực nó. Chắc chắn, một số hương vị regex có thể so khớp đệ quy các mẫu * (và có thể khớp với JSON), nhưng các mẫu kết quả rất kinh khủng khi nhìn vào và không bao giờ được sử dụng trong mã sản xuất IMO!
* Tuy nhiên, hãy lưu ý, nhiều triển khai regex không hỗ trợ các mẫu đệ quy. Trong số các ngôn ngữ lập trình phổ biến, những ngôn ngữ này hỗ trợ các mẫu đệ quy: Perl, .NET, PHP và Ruby 1.9.2
Tôi đã thử câu trả lời của @ mario, nhưng nó không hiệu quả với tôi, vì tôi đã tải xuống bộ thử nghiệm từ JSON.org ( kho lưu trữ ) và có 4 lần kiểm tra không thành công (fail1.json, fail18.json, fail25.json, fail27. json).
Tôi đã điều tra các lỗi và phát hiện ra, điều đó fail1.json
thực sự đúng (theo ghi chú của hướng dẫn sử dụng và chuỗi hợp lệ RFC-7159 cũng là một JSON hợp lệ). Tệp fail18.json
cũng không phải là trường hợp, vì nó chứa JSON lồng sâu thực sự đúng:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Vì vậy, hai tệp còn lại: fail25.json
và fail27.json
:
[" tab character in string "]
và
["line
break"]
Cả hai đều chứa các ký tự không hợp lệ. Vì vậy, tôi đã cập nhật mẫu như thế này (chuỗi con được cập nhật):
$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';
Vì vậy, bây giờ tất cả các bài kiểm tra pháp lý từ json.org có thể được thông qua.
Nhìn vào tài liệu cho JSON , có vẻ như regex có thể chỉ đơn giản là ba phần nếu mục tiêu chỉ là để kiểm tra tính phù hợp:
[]
hoặc{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Tất cả cùng nhau:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Nếu chuỗi JSON chứa các newline
ký tự, thì bạn nên sử dụng công singleline
tắc trên hương vị regex của mình để .
khớp newline
. Xin lưu ý rằng điều này sẽ không thất bại trên tất cả JSON xấu, nhưng sẽ không thành công nếu cấu trúc JSON cơ bản không hợp lệ, đây là một cách đơn giản để thực hiện xác thực sanity cơ bản trước khi chuyển nó cho trình phân tích cú pháp.
Tôi đã tạo một triển khai Ruby của giải pháp Mario, giải pháp này hoạt động:
# 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
Đối với "chuỗi và số", tôi nghĩ rằng biểu thức chính quy một phần cho số:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
thay vào đó nên là:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
vì phần thập phân của số là tùy chọn và cũng có thể an toàn hơn nếu thoát -
ký hiệu vào [+-]
vì nó có ý nghĩa đặc biệt giữa dấu ngoặc
\d
rất nguy hiểm. Trong nhiều triển khai regexp \d
khớp với định nghĩa Unicode của một chữ số không chỉ [0-9]
mà thay vào đó bao gồm các tập lệnh thay thế.
Dấu phẩy ở cuối trong mảng JSON đã khiến Perl 5.16 của tôi bị treo, có thể do nó liên tục bẻ khóa lại. Tôi đã phải thêm một chỉ thị chấm dứt backtrack:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
Bằng cách này, khi nó xác định một cấu trúc không phải là 'tùy chọn' ( *
hoặc ?
), nó không nên thử quay ngược lại nó để cố gắng xác định nó là một thứ khác.
Như đã viết ở trên, nếu ngôn ngữ bạn sử dụng có thư viện JSON đi kèm, hãy sử dụng nó để thử giải mã chuỗi và bắt lỗi / ngoại lệ nếu nó không thành công! Nếu ngôn ngữ không (chỉ gặp trường hợp như vậy với FreeMarker) thì regex sau ít nhất có thể cung cấp một số xác thực rất cơ bản (nó được viết cho PHP / PCRE để có thể kiểm tra / sử dụng được cho nhiều người dùng hơn). Nó không đáng sợ như giải pháp được chấp nhận, nhưng cũng không đáng sợ như vậy =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
giải thích ngắn gọn:
// 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)
nếu tôi bỏ lỡ một cái gì đó sẽ làm hỏng điều này ngoài ý muốn, tôi rất biết ơn các ý kiến đóng góp!
nó xác thực 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"
}
]
}
}
Đây là regexp của tôi để xác thực chuỗi:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Đã được viết usign sơ đồ cú pháp ban đầu .