Saya mencari Regex yang memungkinkan saya memvalidasi json.
Saya sangat baru mengenal Regex dan saya cukup tahu bahwa parsing dengan Regex itu buruk tetapi dapatkah itu digunakan untuk memvalidasi?
Saya mencari Regex yang memungkinkan saya memvalidasi json.
Saya sangat baru mengenal Regex dan saya cukup tahu bahwa parsing dengan Regex itu buruk tetapi dapatkah itu digunakan untuk memvalidasi?
Jawaban:
Sebagian besar implementasi regex modern memungkinkan ekspresi ekspresi rekursif, yang dapat memverifikasi struktur serial JSON lengkap. The spesifikasi json.org membuatnya cukup mudah.
$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
';
Ia bekerja cukup baik di PHP dengan fungsi PCRE . Harus bekerja tanpa modifikasi di Perl; dan tentunya bisa diadaptasi untuk bahasa lain. Juga berhasil dengan kasus uji JSON .
Pendekatan yang lebih sederhana adalah pemeriksaan konsistensi minimal seperti yang ditentukan dalam RFC4627, bagian 6 . Namun ini hanya dimaksudkan sebagai uji keamanan dan tindakan pencegahan non-validitas dasar:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
cocok sedangkan nilai JSON tingkat atas harus berupa larik atau objek. Ini juga memiliki banyak masalah dalam kumpulan karakter yang diperbolehkan dalam string atau dalam spasi.
Ya, ini adalah kesalahpahaman umum bahwa Ekspresi Reguler hanya dapat cocok dengan bahasa biasa . Faktanya, fungsi PCRE dapat mencocokkan lebih dari bahasa biasa , mereka dapat mencocokkan bahkan beberapa bahasa non-konteks-bebas! Artikel Wikipedia tentang RegExps memiliki bagian khusus tentangnya.
JSON dapat dikenali menggunakan PCRE dengan beberapa cara! @mario menunjukkan satu solusi hebat menggunakan subpattern dan referensi latar bernama . Kemudian dia mencatat bahwa harus ada solusi dengan menggunakan pola rekursif (?R)
. Berikut adalah contoh regexp yang ditulis dalam 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';
Saya menggunakan (?1)
bukannya (?R)
karena yang terakhir mereferensikan seluruh pola, tetapi kami memiliki \A
dan \Z
urutan yang tidak boleh digunakan di dalam subpattern.(?1)
referensi ke regexp yang ditandai dengan tanda kurung terluar (inilah mengapa yang paling luar ( )
tidak dimulai dengan ?:
). Jadi, RegExp menjadi 268 karakter :)
/\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
Bagaimanapun, ini harus diperlakukan sebagai "demonstrasi teknologi", bukan sebagai solusi praktis. Di PHP saya akan memvalidasi string JSON dengan memanggil json_decode()
fungsi (seperti yang dicatat @Epcylon). Jika saya akan menggunakan JSON itu (jika divalidasi), maka ini adalah metode terbaik.
\d
itu berbahaya. Dalam banyak implementasi regexp \d
cocok dengan definisi Unicode dari sebuah digit yang tidak hanya [0-9]
berisi skrip alternatif.
\d
tidak cocok dengan nomor unicode dalam implementasi PHP PCRE. Misalnya ٩
simbol (0x669 arabic-indic digit sembilan) akan dicocokkan menggunakan pola #\p{Nd}#u
tetapi tidak#\d#u
/u
bendera. JSON dikodekan dalam UTF-8. Untuk ekspresi reguler yang tepat, Anda harus menggunakan tanda itu.
u
pengubah, harap lihat kembali pola di komentar saya sebelumnya :) String, angka, dan boolean SUDAH cocok dengan benar di tingkat atas. Anda dapat menempelkan ekspresi reguler panjang di sini quanetic.com/Regex dan coba sendiri
Karena sifat rekursif JSON (nested {...}
-s), regex tidak cocok untuk memvalidasinya. Tentu, beberapa ragam ekspresi reguler dapat mencocokkan pola * secara rekursif (dan karenanya dapat mencocokkan JSON), tetapi pola yang dihasilkan sangat mengerikan untuk dilihat, dan tidak boleh digunakan dalam kode produksi IMO!
* Hati-hati, banyak implementasi regex tidak mendukung pola rekursif. Dari bahasa pemrograman populer, ini mendukung pola rekursif: Perl, .NET, PHP dan Ruby 1.9.2
Saya mencoba jawaban @ mario, tetapi tidak berhasil untuk saya, karena saya telah mengunduh rangkaian pengujian dari JSON.org ( arsip ) dan ada 4 pengujian yang gagal (fail1.json, fail18.json, fail25.json, fail27. json).
Saya telah menyelidiki kesalahan dan menemukan, itu fail1.json
sebenarnya benar (menurut catatan manual dan string valid RFC-7159 juga merupakan JSON yang valid). File fail18.json
juga bukan masalahnya, karena file berisi JSON bertingkat dalam yang benar:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Jadi dua file tersisa: fail25.json
dan fail27.json
:
[" tab character in string "]
dan
["line
break"]
Keduanya mengandung karakter yang tidak valid. Jadi saya telah memperbarui pola seperti ini (string subpattern diperbarui):
$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';
Jadi sekarang semua tes hukum dari json.org dapat dilalui.
Melihat dokumentasi untuk JSON , tampaknya regex dapat menjadi tiga bagian jika tujuannya hanya untuk memeriksa kesesuaian:
[]
atau{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Bersama:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Jika string JSON berisi newline
karakter, Anda harus menggunakan singleline
sakelar pada ragam regex Anda agar .
cocok newline
. Harap dicatat bahwa ini tidak akan gagal pada semua JSON yang buruk, tetapi akan gagal jika struktur JSON dasar tidak valid, yang merupakan cara langsung untuk melakukan validasi kewarasan dasar sebelum meneruskannya ke parser.
Saya membuat implementasi Ruby dari solusi Mario, yang berfungsi:
# 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
Untuk "string dan angka", menurut saya ekspresi reguler parsial untuk angka:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
seharusnya sebagai gantinya:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
karena bagian desimal dari angka tersebut adalah opsional, dan juga mungkin lebih aman untuk melepaskan -
simbol [+-]
karena memiliki arti khusus di antara tanda kurung
\d
itu berbahaya. Dalam banyak implementasi regexp \d
cocok dengan definisi Unicode dari sebuah digit yang tidak hanya [0-9]
berisi skrip alternatif.
Tanda koma dalam larik JSON menyebabkan Perl 5.16 saya hang, mungkin karena terus mundur. Saya harus menambahkan arahan penghentian mundur:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
Dengan cara ini, setelah mengidentifikasi konstruksi yang bukan 'opsional' ( *
atau ?
), ia tidak boleh mencoba mundur untuk mencoba mengidentifikasinya sebagai sesuatu yang lain.
Seperti yang ditulis di atas, jika bahasa yang Anda gunakan memiliki perpustakaan JSON yang menyertainya, gunakan untuk mencoba mendekode string dan menangkap pengecualian / kesalahan jika gagal! Jika bahasanya tidak (hanya memiliki kasus seperti itu dengan FreeMarker), regex berikut setidaknya dapat memberikan beberapa validasi yang sangat dasar (itu ditulis untuk PHP / PCRE agar dapat diuji / digunakan untuk lebih banyak pengguna). Ini tidak semudah solusi yang diterima, tetapi juga tidak terlalu menakutkan =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
penjelasan singkat:
// 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)
jika saya melewatkan sesuatu yang akan merusak ini secara tidak sengaja, saya berterima kasih atas komentar!
itu memvalidasi kunci (string): nilai (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"
}
]
}
}
Di sini regexp saya untuk memvalidasi string:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Ditulis menggunakan diagram sintaks asli .
Saya menyadari bahwa ini lebih dari 6 tahun yang lalu. Namun, saya pikir ada solusi yang tidak disebutkan oleh siapa pun di sini yang jauh lebih mudah daripada regexing
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}