Je recherche un Regex qui me permette de valider json.
Je suis très nouveau dans Regex et je sais assez que l'analyse avec Regex est mauvaise mais peut-elle être utilisée pour valider?
Je recherche un Regex qui me permette de valider json.
Je suis très nouveau dans Regex et je sais assez que l'analyse avec Regex est mauvaise mais peut-elle être utilisée pour valider?
Réponses:
La plupart des implémentations modernes de regex permettent des expressions régulières récursives, qui peuvent vérifier une structure sérialisée JSON complète. La spécification json.org le rend assez simple.
$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
';
Cela fonctionne assez bien en PHP avec les fonctions PCRE . Devrait fonctionner sans modification en Perl; et peut certainement être adapté pour d'autres langues. Il réussit également avec les cas de test JSON .
Une approche plus simple est le contrôle de cohérence minimal tel que spécifié dans la RFC4627, section 6 . Il s'agit cependant simplement d'un test de sécurité et d'une précaution de base de non-validité:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
correspond alors que la valeur JSON de niveau supérieur doit être un tableau ou un objet. Il a également de nombreux problèmes dans le jeu de caractères autorisé dans les chaînes ou dans les espaces.
Oui, c'est une idée fausse courante que les expressions régulières ne peuvent correspondre qu'à des langues régulières . En fait, les fonctions PCRE peuvent correspondre beaucoup plus que les langues ordinaires , elles peuvent même correspondre à certaines langues non sans contexte! L'article de Wikipedia sur RegExps a une section spéciale à ce sujet.
JSON peut être reconnu en utilisant PCRE de plusieurs manières! @mario a montré une excellente solution utilisant des sous-modèles nommés et des références arrière . Puis il a noté qu'il devrait y avoir une solution utilisant des modèles récursifs (?R)
. Voici un exemple d'une telle expression rationnelle écrite en 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';
J'utilise au (?1)
lieu de (?R)
parce que celui - ci fait référence à l' ensemble du modèle, mais nous avons \A
et des \Z
séquences qui ne doivent pas être utilisés à l' intérieur des sous - modèles. (?1)
des références à l'expression rationnelle marquée par les parenthèses les plus externes (c'est pourquoi la plus externe ( )
ne commence pas par ?:
). Ainsi, le RegExp devient 268 caractères de long :)
/\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
Quoi qu'il en soit, cela devrait être traité comme une "démonstration technologique", et non comme une solution pratique. En PHP, je validerai la chaîne JSON en appelant la json_decode()
fonction (tout comme @Epcylon l'a noté). Si je vais utiliser ce JSON (s'il est validé), alors c'est la meilleure méthode.
\d
est dangereuse. Dans de nombreuses implémentations de regexp \d
correspond à la définition Unicode d'un chiffre qui n'est pas seulement [0-9]
mais inclut à la place des scripts alternatifs.
\d
cela ne correspond pas aux nombres Unicode dans l'implémentation PHP de PCRE. Par exemple, le ٩
symbole (0x669 chiffre neuf de l'indic arabe) sera mis en correspondance en utilisant le modèle #\p{Nd}#u
mais pas#\d#u
/u
drapeau. JSON est encodé en UTF-8. Pour une expression rationnelle correcte, vous devez utiliser cet indicateur.
u
modificateur, veuillez revoir les modèles dans mon commentaire précédent :) Les chaînes, les nombres et les booléens SONT correctement appariés au niveau supérieur. Vous pouvez coller la longue expression régulière ici quanetic.com/Regex et essayer vous
En raison de la nature récursive de JSON (imbriqué {...}
-s), regex n'est pas adapté pour le valider. Bien sûr, certaines saveurs de regex peuvent correspondre récursivement à des modèles * (et peuvent donc correspondre à JSON), mais les modèles résultants sont horribles à regarder et ne devraient jamais être utilisés dans le code de production IMO!
* Attention cependant, de nombreuses implémentations de regex ne prennent pas en charge les modèles récursifs. Parmi les langages de programmation populaires, ceux-ci prennent en charge les modèles récursifs: Perl, .NET, PHP et Ruby 1.9.2
J'ai essayé la réponse de @ mario, mais cela n'a pas fonctionné pour moi, car j'ai téléchargé la suite de tests de JSON.org ( archive ) et il y a eu 4 tests qui ont échoué (fail1.json, fail18.json, fail25.json, fail27. json).
J'ai enquêté sur les erreurs et découvert que fail1.json
c'est en fait correct (selon la note du manuel et la chaîne valide RFC-7159 est également un JSON valide). Le fichier fail18.json
n'était pas non plus le cas, car il contient en fait un JSON profondément imbriqué correct:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Il reste donc deux fichiers: fail25.json
et fail27.json
:
[" tab character in string "]
et
["line
break"]
Les deux contiennent des caractères non valides. J'ai donc mis à jour le modèle comme ceci (sous-modèle de chaîne mis à jour):
$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';
Alors maintenant, tous les tests juridiques de json.org peuvent être passés.
En regardant la documentation pour JSON , il semble que l'expression régulière peut simplement être en trois parties si le but est simplement de vérifier la forme:
[]
ou{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Tous ensemble:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Si la chaîne JSON contient des newline
caractères, vous devez utiliser le singleline
commutateur sur votre saveur regex pour que cela .
corresponde newline
. Veuillez noter que cela n'échouera pas sur tous les mauvais JSON, mais cela échouera si la structure JSON de base n'est pas valide, ce qui est un moyen simple de faire une validation de base de la cohérence avant de la transmettre à un analyseur.
J'ai créé une implémentation Ruby de la solution de Mario, qui fonctionne:
# 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
Pour "chaînes et nombres", je pense que l'expression régulière partielle pour les nombres:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
devrait être à la place:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
car la partie décimale du nombre est facultative, et il est probablement plus sûr d'échapper au -
symbole [+-]
car il a une signification particulière entre crochets
\d
est dangereuse. Dans de nombreuses implémentations de regexp \d
correspond à la définition Unicode d'un chiffre qui n'est pas seulement [0-9]
mais inclut à la place des scripts alternatifs.
Une virgule de fin dans un tableau JSON a provoqué le blocage de Perl 5.16, probablement parce qu'il continuait à revenir en arrière. J'ai dû ajouter une directive de fin de retour:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
De cette façon, une fois qu'il identifie une construction qui n'est pas «facultative» ( *
ou ?
), il ne devrait pas essayer de revenir en arrière pour essayer de l'identifier comme autre chose.
Comme il a été écrit ci-dessus, si le langage que vous utilisez a une bibliothèque JSON, utilisez-la pour essayer de décoder la chaîne et attraper l'exception / erreur si elle échoue! Si le langage ne le fait pas (juste un tel cas avec FreeMarker), le regex suivant pourrait au moins fournir une validation très basique (il est écrit pour que PHP / PCRE soit testable / utilisable pour plus d'utilisateurs). Ce n'est pas aussi infaillible que la solution acceptée, mais pas aussi effrayant =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
courte explication:
// 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)
si je manquais quelque chose qui risquerait de briser ce problème involontairement, je suis reconnaissant pour les commentaires!
il valide la clé (chaîne): valeur (chaîne, entier, [{clé: valeur}, {clé: valeur}], {clé: valeur})
^\{(\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"
}
]
}
}
Voici mon expression régulière pour valider la chaîne:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
A été écrit en utilisant le diagramme de syntaxe original .
Je me rends compte que cela remonte à plus de 6 ans. Cependant, je pense qu'il existe une solution que personne ici n'a mentionnée et qui est beaucoup plus facile que le regexing
function isAJSON(string) {
try {
JSON.parse(string)
} catch(e) {
if(e instanceof SyntaxError) return false;
};
return true;
}