ฉันกำลังมองหา Regex ที่ช่วยให้ฉันตรวจสอบ json ได้
ฉันใหม่มากสำหรับ Regex และฉันรู้ดีพอว่าการแยกวิเคราะห์ด้วย Regex นั้นไม่ดี แต่สามารถใช้ตรวจสอบความถูกต้องได้หรือไม่
ฉันกำลังมองหา Regex ที่ช่วยให้ฉันตรวจสอบ json ได้
ฉันใหม่มากสำหรับ Regex และฉันรู้ดีพอว่าการแยกวิเคราะห์ด้วย Regex นั้นไม่ดี แต่สามารถใช้ตรวจสอบความถูกต้องได้หรือไม่
คำตอบ:
การใช้งาน regex ที่ทันสมัยส่วนใหญ่อนุญาตให้ใช้นิพจน์ซ้ำซึ่งสามารถตรวจสอบโครงสร้างอนุกรม 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
';
มันทำงานได้ค่อนข้างดีใน PHP กับฟังก์ชั่น PCRE ควรใช้งานที่ไม่มีการแก้ไขใน 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มีส่วนพิเศษเกี่ยวกับเรื่องนี้
JSON สามารถรับรู้โดยใช้ PCRE ได้หลายวิธี! @mario แสดงให้เห็นว่าหนึ่งในทางออกที่ดีในการใช้ subpatterns ชื่อและกลับอ้างอิง จากนั้นเขาก็ตั้งข้อสังเกตว่าควรจะมีวิธีการแก้ปัญหาโดยใช้รูปแบบ recursive (?R)
นี่คือตัวอย่างของ regexp ที่เขียนด้วย 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 ที่ทำเครื่องหมายโดยวงเล็บด้านนอกสุด (นี่คือสาเหตุที่ตัวนอกสุด( )
ไม่ขึ้นต้นด้วย?:
) ดังนั้น 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 ด้วยการเรียกใช้json_decode()
ฟังก์ชัน (เช่นเดียวกับที่ @Epcylon ระบุไว้) ถ้าฉันจะใช้ JSON นั้น (หากผ่านการตรวจสอบแล้ว) นี่เป็นวิธีที่ดีที่สุด
\d
เป็นอันตราย ในการใช้งาน regexp จำนวนมาก\d
ตรงกับนิยาม Unicode ของตัวเลขที่ไม่เพียง[0-9]
แต่รวมสคริปต์ทางเลือกแทน
\d
ไม่ตรงกับหมายเลข Unicode ในการใช้งาน PCRE ของ PHP ตัวอย่างเช่น٩
สัญลักษณ์ (0x669 หลักที่บ่งบอกภาษาอาหรับเก้า) จะจับคู่โดยใช้รูปแบบ#\p{Nd}#u
แต่ไม่ใช่#\d#u
/u
ธง JSON เข้ารหัสเป็น UTF-8 สำหรับ regexp ที่เหมาะสมคุณควรใช้แฟล็กนั้น
u
ร์โปรดดูรูปแบบในความคิดเห็นก่อนหน้านี้อีกครั้ง :) สตริงตัวเลขและบูลีนตรงกันอย่างถูกต้องที่ระดับบนสุด คุณสามารถวาง regexp แบบยาวได้ที่นี่quanetic.com/Regexและลองด้วยตัวเอง
เนื่องจากลักษณะการเรียกซ้ำของ JSON ( {...}
-s ที่ซ้อนกัน) regex จึงไม่เหมาะที่จะตรวจสอบความถูกต้อง แน่นอนว่ารสชาติ regex บางอย่างสามารถจับคู่รูปแบบซ้ำได้* (และสามารถจับคู่ JSON ได้) แต่รูปแบบที่ได้นั้นดูน่ากลัวและไม่ควรใช้ในรหัสการผลิต IMO!
* โปรดระวังการใช้ regex จำนวนมากไม่สนับสนุนรูปแบบการเรียกซ้ำ ภาษาโปรแกรมยอดนิยมเหล่านี้รองรับรูปแบบการเรียกซ้ำ: Perl, .NET, PHP และ Ruby 1.9.2
ฉันลองคำตอบของ @ mario แล้ว แต่มันไม่ได้ผลสำหรับฉันเพราะฉันดาวน์โหลดชุดทดสอบจาก JSON.org ( เก็บถาวร ) และมีการทดสอบที่ล้มเหลว 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ดูเหมือนว่า regex สามารถเป็นสามส่วนได้หากเป้าหมายคือการตรวจสอบความเหมาะสม:
[]
หรือ{}
[{\[]{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 พื้นฐานไม่ถูกต้องซึ่งเป็นวิธีตรงไปตรงมาในการตรวจสอบความถูกต้องพื้นฐานก่อนส่งต่อไปยังโปรแกรมแยกวิเคราะห์
[{\[]{1}([,:{}\[\]0-9.\-+A-zr-u \n\r\t]|".*:?")+[}\]]{1}
ฉันสร้างการใช้งาน Ruby ของโซลูชันของ Mario ซึ่งใช้งานได้:
# 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
สำหรับ "สตริงและตัวเลข" ฉันคิดว่านิพจน์ทั่วไปบางส่วนสำหรับตัวเลข:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
ควรเป็นแทน:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
เนื่องจากส่วนทศนิยมของตัวเลขเป็นทางเลือกและอาจปลอดภัยกว่าที่จะหลีกเลี่ยง-
สัญลักษณ์[+-]
เนื่องจากมีความหมายพิเศษระหว่างวงเล็บ
\d
เป็นอันตราย ในการใช้งาน regexp จำนวนมาก\d
ตรงกับนิยาม Unicode ของตัวเลขที่ไม่เพียง[0-9]
แต่รวมสคริปต์ทางเลือกแทน
เครื่องหมายจุลภาคต่อท้ายในอาร์เรย์ JSON ทำให้ Perl 5.16 ของฉันหยุดทำงานอาจเป็นเพราะมันยังคงย้อนกลับ ฉันต้องเพิ่มคำสั่งยุติการย้อนรอย:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
ด้วยวิธีนี้เมื่อระบุโครงสร้างที่ไม่ใช่ 'ทางเลือก' ( *
หรือ?
) แล้วก็ไม่ควรลองย้อนรอยเพื่อพยายามระบุว่าเป็นสิ่งอื่น
ตามที่เขียนไว้ข้างต้นหากภาษาที่คุณใช้มีไลบรารี JSON มาด้วยให้ใช้เพื่อลองถอดรหัสสตริงและตรวจจับข้อยกเว้น / ข้อผิดพลาดหากล้มเหลว! หากภาษาไม่มี (เพิ่งมีกรณีเช่นนี้กับ FreeMarker) อย่างน้อย 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)
หากฉันพลาดสิ่งที่จะทำลายสิ่งนี้โดยไม่ได้ตั้งใจฉันขอบคุณสำหรับความคิดเห็น!
ตรวจสอบความถูกต้องของคีย์ (สตริง): ค่า (สตริง, จำนวนเต็ม, [{คีย์: ค่า}, {คีย์: ค่า}], {คีย์: ค่า})
^\{(\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"
}
]
}
}
นี่คือ regexp ของฉันสำหรับตรวจสอบสตริง:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
ถูกเขียน usign Diagramm ไวยากรณ์เดิม
ฉันตระหนักดีว่านี่มาจาก 6 ปีที่แล้ว อย่างไรก็ตามฉันคิดว่ามีวิธีแก้ปัญหาที่ไม่มีใครพูดถึงว่าง่ายกว่า regexing
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}