Szukam Regex, który pozwala mi zweryfikować json.
Jestem bardzo nowy w Regex's i wiem wystarczająco dużo, że parsowanie z Regex jest złe, ale czy można go użyć do walidacji?
Szukam Regex, który pozwala mi zweryfikować json.
Jestem bardzo nowy w Regex's i wiem wystarczająco dużo, że parsowanie z Regex jest złe, ale czy można go użyć do walidacji?
Odpowiedzi:
Większość nowoczesnych implementacji regex pozwala na rekurencyjne wyrażenia rekurencyjne, które mogą zweryfikować pełną zserializowaną strukturę JSON. Specyfikacja json.org czyni to całkiem prostym.
$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
';
Działa całkiem dobrze w PHP z funkcjami PCRE . Powinien działać niezmodyfikowany w Perlu; iz pewnością można je dostosować do innych języków. Udaje się również z przypadkami testowymi JSON .
Prostszym podejściem jest sprawdzenie minimalnej spójności, jak określono w dokumencie RFC4627, sekcja 6 . Jest to jednak przeznaczone tylko jako test bezpieczeństwa i podstawowe zabezpieczenie przed utratą ważności:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
pasuje, podczas gdy wartość JSON najwyższego poziomu musi być tablicą lub obiektem. Ma również wiele problemów w zestawach znaków dozwolonych w łańcuchach lub spacjach.
Tak, powszechne jest błędne przekonanie, że wyrażenia regularne mogą pasować tylko do zwykłych języków . W rzeczywistości funkcje PCRE mogą dopasować znacznie więcej niż zwykłe języki , mogą pasować nawet do niektórych języków niekontekstowych! Artykuł Wikipedii na temat RegExps zawiera specjalną sekcję na ten temat.
JSON można rozpoznać za pomocą PCRE na kilka sposobów! @mario pokazało jedno świetne rozwiązanie, używając nazwanych wzorców podrzędnych i odniesień wstecznych . Następnie zauważył, że powinno być rozwiązanie wykorzystujące wzorce rekurencyjne (?R)
. Oto przykład takiego wyrażenia regularnego napisanego w 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';
Używam (?1)
zamiast, (?R)
ponieważ ten ostatni odwołuje się do całego wzorca, ale mamy \A
i \Z
sekwencje, które nie powinny być używane wewnątrz wzorców podrzędnych. (?1)
odniesienia do wyrażenia regularnego oznaczone skrajnymi nawiasami (dlatego najbardziej zewnętrzne ( )
nie zaczyna się od ?:
). Tak więc wyrażenie RegExp ma długość 268 znaków :)
/\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
Zresztą należy to traktować jako „demonstrację technologii”, a nie praktyczne rozwiązanie. W PHP json_decode()
sprawdzę ciąg JSON przez wywołanie funkcji (tak jak zauważył @Epcylon). Jeśli mam zamiar używać tego formatu JSON (jeśli jest zweryfikowany), to jest to najlepsza metoda.
\d
jest niebezpieczne. W wielu implementacjach regexp \d
dopasowuje definicję Unicode cyfry, która nie jest tylko, [0-9]
ale zamiast tego zawiera alternatywne skrypty.
\d
nie pasuje do numerów Unicode w implementacji PCRE w PHP. Na przykład ٩
symbol (0x669 arabsko-indyjska cyfra dziewięć) zostanie dopasowany za pomocą wzorca, #\p{Nd}#u
ale nie#\d#u
/u
flagi. JSON jest zakodowany w UTF-8. Aby uzyskać prawidłowe wyrażenie regularne, powinieneś użyć tej flagi.
u
modyfikatora, spójrz jeszcze raz na wzorce w moim poprzednim komentarzu :) Ciągi znaków, liczby i wartości logiczne SĄ poprawnie dopasowane na najwyższym poziomie. Możesz wkleić długie wyrażenie regularne tutaj quanetic.com/Regex i spróbować samemu
Ze względu na rekursywny charakter formatu JSON (zagnieżdżone {...}
-s) wyrażenie regularne nie nadaje się do jego weryfikacji. Jasne, niektóre odmiany wyrażeń regularnych mogą rekursywnie dopasowywać wzorce * (i dlatego mogą pasować do JSON), ale wynikowe wzorce są straszne i nigdy nie powinny być używane w produkcyjnym kodzie IMO!
* Uważaj jednak, wiele implementacji regex nie obsługuje wzorców rekurencyjnych. Spośród popularnych języków programowania obsługują one wzorce rekurencyjne: Perl, .NET, PHP i Ruby 1.9.2
Wypróbowałem odpowiedź @ mario, ale nie zadziałała, ponieważ pobrałem pakiet testów z JSON.org ( archiwum ) i były 4 nieudane testy (fail1.json, fail18.json, fail25.json, fail27. json).
Zbadałem błędy i odkryłem, że fail1.json
jest to rzeczywiście poprawne (zgodnie z uwagą instrukcji i prawidłowym ciągiem znaków RFC-7159 jest również prawidłowym JSON). Plik też fail18.json
nie był przypadkiem, ponieważ zawiera faktycznie poprawne głęboko zagnieżdżone JSON:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Zostały więc dwa pliki: fail25.json
i fail27.json
:
[" tab character in string "]
i
["line
break"]
Obie zawierają nieprawidłowe znaki. Więc zaktualizowałem wzorzec w ten sposób (zaktualizowano podwzór łańcucha):
$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';
Więc teraz wszystkie testy prawne z json.org można zaliczyć.
Patrząc na dokumentację JSON , wydaje się, że wyrażenie regularne może składać się po prostu z trzech części, jeśli celem jest tylko sprawdzenie sprawności:
[]
lub{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Wszyscy razem:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Jeśli ciąg JSON zawiera newline
znaki, powinieneś użyć singleline
przełącznika w swoim stylu wyrażenia regularnego, aby .
pasował newline
. Należy pamiętać, że nie zakończy się to niepowodzeniem w przypadku wszystkich złych JSON, ale zakończy się niepowodzeniem, jeśli podstawowa struktura JSON jest nieprawidłowa, co jest prostym sposobem przeprowadzenia podstawowej weryfikacji poprawności przed przekazaniem jej do parsera.
Stworzyłem implementację Ruby rozwiązania Mario, która działa:
# 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
Myślę, że w przypadku „ciągów znaków i liczb” częściowe wyrażenie regularne dla liczb:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
zamiast tego powinno być:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
ponieważ część dziesiętna liczby jest opcjonalna, a także prawdopodobnie bezpieczniej jest uciec przed -
symbolem, [+-]
ponieważ ma on specjalne znaczenie w nawiasach
\d
jest niebezpieczne. W wielu implementacjach regexp \d
dopasowuje definicję Unicode cyfry, która nie jest tylko, [0-9]
ale zamiast tego zawiera alternatywne skrypty.
Końcowy przecinek w tablicy JSON spowodował zawieszenie się mojego Perla 5.16, prawdopodobnie dlatego, że ciągle się cofał. Musiałem dodać dyrektywę kończącą wycofywanie:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
W ten sposób, gdy zidentyfikuje konstrukcję, która nie jest „opcjonalna” ( *
lub ?
), nie powinna próbować cofać się do niej w celu zidentyfikowania jej jako czegoś innego.
Jak napisano powyżej, jeśli używany język ma dołączoną bibliotekę JSON, użyj jej, aby spróbować zdekodować ciąg i złapać wyjątek / błąd, jeśli się nie powiedzie! Jeśli język nie (właśnie miał taki przypadek z FreeMarker), następujące wyrażenie regularne mogłoby przynajmniej zapewnić bardzo podstawową walidację (zostało napisane dla PHP / PCRE, aby było testowalne / użyteczne dla większej liczby użytkowników). Nie jest tak niezawodne jak przyjęte rozwiązanie, ale też nie jest tak straszne =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
krótkie wyjaśnienie:
// 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)
jeśli przegapiłem coś, co mogłoby to nieumyślnie zepsuć, jestem wdzięczny za komentarze!
sprawdza klucz (ciąg): wartość (ciąg, liczba całkowita, [{klucz: wartość}, {klucz: wartość}], {klucz: wartość})
^\{(\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"
}
]
}
}
Tutaj moje wyrażenie regularne do sprawdzania poprawności ciągu:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Został napisany przy użyciu oryginalnego schematu składni .
Zdaję sobie sprawę, że to sprzed ponad 6 lat. Myślę jednak, że istnieje rozwiązanie, o którym nikt tutaj nie wspomniał, a które jest o wiele łatwiejsze niż regexing
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}