Json'u doğrulamama izin veren bir Regex arıyorum.
Regex'ler konusunda çok yeniyim ve Regex ile ayrıştırmanın kötü olduğunu yeterince biliyorum, ancak doğrulama için kullanılabilir mi?
Json'u doğrulamama izin veren bir Regex arıyorum.
Regex'ler konusunda çok yeniyim ve Regex ile ayrıştırmanın kötü olduğunu yeterince biliyorum, ancak doğrulama için kullanılabilir mi?
Yanıtlar:
Modern regex uygulamalarının çoğu, eksiksiz bir JSON serileştirilmiş yapıyı doğrulayabilen yinelemeli yeniden ifadelere izin verir. Json.org şartname oldukça basit hale getirir.
$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'de PCRE işlevleriyle oldukça iyi çalışır . Perl'de değiştirilmeden çalışmalıdır; ve kesinlikle diğer diller için uyarlanabilir. Ayrıca JSON test durumlarında başarılı olur .
Daha basit bir yaklaşım, RFC4627, bölüm 6'da belirtildiği gibi minimum tutarlılık kontrolüdür . Bununla birlikte, yalnızca güvenlik testi ve temel geçerliliğe karşı önlem olarak tasarlanmıştır:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
değer eşleşirken üst düzey JSON değeri bir dizi veya bir nesne olmalıdır. Ayrıca karakter kümesinde dizelerde veya boşluklarda izin verilen birçok sorunu vardır.
Evet, Normal İfadelerin yalnızca normal dillerle eşleşebileceği yaygın bir yanılgıdır . Aslında, PCRE işlevleri normal dillerden çok daha fazlasıyla eşleşebilir, bağlamdan bağımsız bazı dillerle bile eşleşebilirler! Wikipedia'nın RegExps makalesi bununla ilgili özel bir bölüme sahiptir.
JSON, PCRE kullanılarak çeşitli şekillerde tanınabilir! @mario, adlandırılmış alt şablonlar ve geri referanslar kullanarak harika bir çözüm gösterdi . Sonra özyinelemeli kalıplar kullanan bir çözüm olması gerektiğini belirtti (?R)
. İşte PHP ile yazılmış böyle bir regexp örneği:
$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';
Ben kullanıyorum (?1)
yerine (?R)
ikinci referanslar nedeniyle tüm desen, ama biz var \A
ve \Z
alt şablonların içine kullanılmamalıdır dizileri. (?1)
en dıştaki parantezle işaretlenmiş normal ifadeye başvurular (bu nedenle en dıştaki ile ( )
başlamaz ?:
). Böylece, RegExp 268 karakter uzunluğunda olur :)
/\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
Her neyse, bu pratik bir çözüm olarak değil, bir "teknoloji gösterimi" olarak görülmelidir. PHP'de JSON dizgesini json_decode()
işlevi çağırarak doğrulayacağım (aynen @Epcylon'da belirtildiği gibi). Bu JSON'u kullanacaksam (doğrulanmışsa), o zaman bu en iyi yöntemdir.
\d
tehlikelidir. Pek çok regexp uygulamasında \d
, yalnızca [0-9]
değil, bunun yerine alternatif komut dosyaları içeren bir rakamın Unicode tanımıyla eşleşir .
\d
PHP'nin PCRE uygulamasında unicode sayılarıyla eşleşmediğini düşünüyorum . Örneğin ٩
sembol (0x669 arapça işaretli rakam dokuz) desen kullanılarak eşleştirilecek #\p{Nd}#u
ancak eşleşmeyecek#\d#u
/u
Bayrağı kullanmadığınız için değil . JSON, UTF-8 olarak kodlanmıştır. Düzgün bir regexp için bu bayrağı kullanmalısınız.
u
Değiştiriciyi kullandım , lütfen önceki yorumumdaki kalıplara tekrar bakın :) Dizeler, sayılar ve boole'lar en üst seviyede doğru şekilde eşleşiyor. Uzun regexp'i buraya quanetic.com/Regex yapıştırabilir ve kendinizi deneyebilirsiniz
JSON'un (iç içe geçmiş {...}
-s) yinelemeli doğası nedeniyle , normal ifade onu doğrulamak için uygun değildir. Elbette, bazı normal ifade çeşitleri, kalıplarla * tekrar tekrar eşleşebilir (ve bu nedenle JSON ile eşleşebilir), ancak ortaya çıkan kalıplara bakmak korkunçtur ve asla IMO üretim kodunda kullanılmamalıdır!
* Olsa birçok regex uygulamalar değil Dikkat değil özyinelemeli desenleri destekler. Popüler programlama dillerinden bunlar yinelemeli kalıpları destekler: Perl, .NET, PHP ve Ruby 1.9.2
@ Mario'nun yanıtını denedim, ancak benim için işe yaramadı, çünkü JSON.org'dan ( arşiv ) test paketini indirdim ve 4 başarısız test vardı (fail1.json, fail18.json, fail25.json, fail27. json).
Hataları araştırdım ve fail1.json
bunun aslında doğru olduğunu buldum (kılavuzun notuna ve RFC-7159 geçerli dizesinin de geçerli bir JSON olduğunu). Dosya da fail18.json
böyle değildi, çünkü aslında doğru derinlemesine yerleştirilmiş JSON içeriyor:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Yani iki dosya kaldı: fail25.json
ve fail27.json
:
[" tab character in string "]
ve
["line
break"]
Her ikisi de geçersiz karakterler içeriyor. Bu yüzden kalıbı şu şekilde güncelledim (dize alt şablonu güncellendi):
$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';
Artık json.org'daki tüm yasal testler geçilebilir.
JSON belgelerine bakıldığında , amaç sadece uygunluğu kontrol etmekse normal ifadenin üç bölümden oluşabileceği görülmektedir:
[]
{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Hep birlikte:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
JSON dizesi newline
karakterler içeriyorsa singleline
, .
eşleşmesi için normal ifade çeşidinizdeki anahtarı kullanmanız gerekir newline
. Lütfen bunun tüm bozuk JSON'larda başarısız olmayacağını, ancak temel JSON yapısı geçersizse başarısız olacağını unutmayın; bu, bir ayrıştırıcıya geçmeden önce temel bir mantık doğrulaması yapmanın basit bir yoludur.
[{\[]{1}([,:{}\[\]0-9.\-+A-zr-u \n\r\t]|".*:?")+[}\]]{1}
Mario'nun çözümünün bir Ruby uygulamasını yarattım, bu işe yarıyor:
# 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
"Dizeler ve sayılar" için, sayılar için kısmi normal ifadenin:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
bunun yerine şöyle olmalıdır:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
sayının ondalık kısmı isteğe bağlı olduğundan ve ayrıca parantezler arasında özel bir anlamı olduğu için -
sembolden kaçmak muhtemelen daha güvenlidir[+-]
\d
tehlikelidir. Pek çok regexp uygulamasında \d
, yalnızca [0-9]
değil, bunun yerine alternatif komut dosyaları içeren bir rakamın Unicode tanımıyla eşleşir .
JSON dizisindeki son virgül, Perl 5.16'mın askıda kalmasına neden oldu, çünkü muhtemelen geriye doğru izlemeyi sürdürüyordu. Bir geri izleme sonlandırma yönergesi eklemem gerekiyordu:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
Bu şekilde, 'isteğe bağlı' ( *
veya ?
) olmayan bir yapıyı tanımladığında , onu başka bir şey olarak tanımlamaya çalışmak için geriye dönmeyi denememelidir.
Yukarıda yazıldığı gibi, kullandığınız dilde bir JSON kitaplığı geliyorsa, dizeyi çözmeyi denemek için kullanın ve başarısız olursa istisnayı / hatayı yakalayın! Dil yoksa (FreeMarker'da böyle bir durum söz konusuysa) aşağıdaki normal ifade en azından bazı çok temel doğrulama sağlayabilir (PHP / PCRE'nin daha fazla kullanıcı için test edilebilir / kullanılabilir olması için yazılmıştır). Kabul edilen çözüm kadar kusursuz değil, aynı zamanda o kadar da korkutucu değil =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
kısa açıklama:
// 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)
Bunu istemeden bozacak bir şeyi kaçırırsam, yorumlar için minnettarım!
anahtarı (dize) doğrular: değer (dize, tam sayı, [{anahtar: değer}, {anahtar: değer}], {anahtar: değer})
^\{(\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"
}
]
}
}
Burada doğrulama dizesi için normal ifadem:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Orijinal sözdizimi diyagramı usign yazılmıştır .
Bunun 6 yıldan fazla bir süre önce olduğunun farkındayım. Bununla birlikte, burada kimsenin bahsetmediği ve normal ifadelerden çok daha kolay bir çözüm olduğunu düşünüyorum.
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}