Ich suche einen Regex, mit dem ich json validieren kann.
Ich bin sehr neu bei Regex und ich weiß genug, dass das Parsen mit Regex schlecht ist, aber kann es zur Validierung verwendet werden?
Ich suche einen Regex, mit dem ich json validieren kann.
Ich bin sehr neu bei Regex und ich weiß genug, dass das Parsen mit Regex schlecht ist, aber kann es zur Validierung verwendet werden?
Antworten:
Die meisten modernen Regex-Implementierungen ermöglichen rekursive Regexpressions, mit denen eine vollständige serialisierte JSON-Struktur überprüft werden kann. Die json.org-Spezifikation macht es ziemlich einfach.
$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
';
Es funktioniert ziemlich gut in PHP mit den PCRE-Funktionen . Sollte in Perl unverändert funktionieren; und kann sicherlich für andere Sprachen angepasst werden. Auch mit den JSON-Testfällen gelingt es .
Ein einfacherer Ansatz ist die minimale Konsistenzprüfung gemäß RFC4627, Abschnitt 6 . Es ist jedoch nur als Sicherheitstest und grundlegende Vorsichtsmaßnahme gegen Nichtgültigkeit gedacht:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
stimmt das einzelne Literal überein, während der JSON-Wert der obersten Ebene entweder ein Array oder ein Objekt sein muss. Es gibt auch viele Probleme im Zeichensatz, die in Zeichenfolgen oder Leerzeichen zulässig sind.
Ja, es ist ein weit verbreitetes Missverständnis, dass reguläre Ausdrücke nur mit regulären Sprachen übereinstimmen können . Tatsächlich können die PCRE-Funktionen viel mehr als normale Sprachen übereinstimmen, sie können sogar mit einigen nicht kontextfreien Sprachen übereinstimmen! Der Wikipedia-Artikel über RegExps enthält einen speziellen Abschnitt dazu.
JSON kann mit PCRE auf verschiedene Arten erkannt werden! @mario zeigte eine großartige Lösung mit benannten Untermustern und Rückverweisen . Dann bemerkte er, dass es eine Lösung mit rekursiven Mustern geben sollte (?R)
. Hier ist ein Beispiel für einen solchen regulären Ausdruck, der in PHP geschrieben wurde:
$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';
Ich verwende (?1)
stattdessen, (?R)
weil letzteres auf das gesamte Muster verweist , aber wir haben \A
und \Z
Sequenzen, die nicht in Untermustern verwendet werden sollten. (?1)
Verweise auf den regulären Ausdruck, der durch die äußersten Klammern gekennzeichnet ist (aus diesem Grund ( )
beginnt der äußerste nicht mit ?:
). So wird der RegExp 268 Zeichen lang :)
/\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
Auf jeden Fall sollte dies als "Technologiedemonstration" behandelt werden, nicht als praktische Lösung. In PHP werde ich die JSON-Zeichenfolge mit dem Aufruf der json_decode()
Funktion validieren (genau wie bei @Epcylon angegeben). Wenn ich werde verwenden , dass JSON (wenn es bestätigt ist), dann ist dies die beste Methode.
\d
ist gefährlich. In vielen Regexp-Implementierungen \d
entspricht die Unicode-Definition einer Ziffer, die nicht nur [0-9]
alternative Skripte enthält, sondern diese auch enthält.
\d
stimmt nicht mit Unicode-Nummern in der PHP-Implementierung von PCRE überein. Zum Beispiel wird das ٩
Symbol (0x669 arabisch-indizierende Ziffer neun) mit dem Muster abgeglichen, #\p{Nd}#u
aber nicht#\d#u
/u
Flagge nicht benutzt hast . JSON ist in UTF-8 codiert. Für einen korrekten regulären Ausdruck sollten Sie dieses Flag verwenden.
u
Modifikator verwendet, bitte schauen Sie sich die Muster in meinem vorherigen Kommentar noch einmal an :) Zeichenfolgen, Zahlen und Boolesche Werte stimmen auf der obersten Ebene korrekt überein. Sie können den langen regulären Ausdruck hier einfügen quanetic.com/Regex und versuchen Sie es selbst
Aufgrund der rekursiven Natur von JSON (verschachtelte {...}
-s) ist Regex nicht zur Validierung geeignet. Sicher, einige Regex-Aromen können rekursiv mit Mustern * übereinstimmen (und können daher mit JSON übereinstimmen), aber die resultierenden Muster sind schrecklich anzusehen und sollten niemals im Produktionscode IMO verwendet werden!
* Obwohl viele regex Implementierungen Vorsicht nicht unterstützen rekursive Muster. Von den gängigen Programmiersprachen unterstützen diese rekursive Muster: Perl, .NET, PHP und Ruby 1.9.2
Ich habe die Antwort von @ mario ausprobiert, aber sie hat bei mir nicht funktioniert, da ich die Testsuite von JSON.org ( Archiv ) heruntergeladen habe und 4 Tests fehlgeschlagen sind (fail1.json, fail18.json, fail25.json, fail27. json).
Ich habe die Fehler untersucht und herausgefunden, dass dies fail1.json
tatsächlich korrekt ist (gemäß dem Hinweis des Handbuchs und der gültigen RFC-7159- Zeichenfolge ist auch ein gültiger JSON). Die Datei fail18.json
war auch nicht der Fall, da sie tatsächlich korrekt tief verschachtelten JSON enthält:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Also noch zwei Dateien übrig: fail25.json
und fail27.json
:
[" tab character in string "]
und
["line
break"]
Beide enthalten ungültige Zeichen. Also habe ich das Muster wie folgt aktualisiert (String-Untermuster aktualisiert):
$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';
Jetzt können alle rechtlichen Tests von json.org bestanden werden.
In der Dokumentation zu JSON scheint es, dass der reguläre Ausdruck einfach aus drei Teilen bestehen kann, wenn das Ziel nur darin besteht, die Fitness zu überprüfen:
[]
oder{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Alle zusammen:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Wenn die JSON-Zeichenfolge newline
Zeichen enthält , sollten Sie den singleline
Schalter für Ihre Regex-Variante verwenden, damit er .
übereinstimmt newline
. Beachten Sie, dass dies nicht bei allen fehlerhaften JSONs fehlschlägt, aber fehlschlägt, wenn die grundlegende JSON-Struktur ungültig ist. Dies ist eine einfache Möglichkeit, eine grundlegende Überprüfung der Integrität durchzuführen, bevor sie an einen Parser übergeben wird.
Ich habe eine Ruby-Implementierung von Marios Lösung erstellt, die funktioniert:
# 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
Für "Strings und Zahlen" denke ich, dass der teilweise reguläre Ausdruck für Zahlen:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
sollte stattdessen sein:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
da der Dezimalteil der Zahl optional ist und es wahrscheinlich auch sicherer ist, das -
Symbol zu maskieren, [+-]
da es eine besondere Bedeutung in Klammern hat
\d
ist gefährlich. In vielen Regexp-Implementierungen \d
entspricht die Unicode-Definition einer Ziffer, die nicht nur [0-9]
alternative Skripte enthält, sondern diese auch enthält.
Ein nachfolgendes Komma in einem JSON-Array ließ mein Perl 5.16 hängen, möglicherweise weil es immer wieder zurückverfolgt wurde. Ich musste eine Backtrack-terminierende Direktive hinzufügen:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
Auf diese Weise sollte es, sobald es ein Konstrukt identifiziert, das nicht 'optional' ( *
oder ?
) ist, nicht versuchen, es zurückzuverfolgen, um es als etwas anderes zu identifizieren.
Wenn die von Ihnen verwendete Sprache eine JSON-Bibliothek enthält, versuchen Sie, wie oben beschrieben, die Zeichenfolge zu dekodieren und die Ausnahme / den Fehler abzufangen, wenn dies fehlschlägt! Wenn die Sprache dies nicht tut (hatte gerade einen solchen Fall mit FreeMarker), könnte der folgende reguläre Ausdruck zumindest eine sehr grundlegende Validierung liefern (er wurde geschrieben, damit PHP / PCRE für mehr Benutzer testbar / verwendbar ist). Es ist nicht so narrensicher wie die akzeptierte Lösung, aber auch nicht so beängstigend =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
kurze Erklärung:
// 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)
Wenn ich etwas verpasst habe, das dies unbeabsichtigt brechen würde, bin ich für Kommentare dankbar!
es validiert Schlüssel (Zeichenfolge): Wert (Zeichenfolge, Ganzzahl, [{Schlüssel: Wert}, {Schlüssel: Wert}], {Schlüssel: Wert})
^\{(\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"
}
]
}
}
Hier mein regulärer Ausdruck für die Validierung der Zeichenfolge:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Wurde unter Verwendung des ursprünglichen Syntaxdiagramms geschrieben .
Mir ist klar, dass dies von vor über 6 Jahren ist. Ich denke jedoch, dass es eine Lösung gibt, die hier niemand erwähnt hat und die viel einfacher ist als Regexing
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}