Regex pour valider JSON

89

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?

Tesson
la source
31
Pourquoi s'embêter avec une étape de validation distincte? La plupart des langages ont des bibliothèques JSON qui peuvent analyser JSON, et s'il peut l'analyser, il était valide. Sinon, la bibliothèque vous le dira.
Epcylon
Vous devez analyser le texte pour le valider ...
Ken
3
@mario - Je ne sais pas ... Je suis tout à fait pour abuser des regex, et extrêmement sympathique à votre objection à l'erreur "regex must match regular" - mais pas sur des questions pratiques liées au travail. La meilleure réponse ici est vraiment le commentaire d'Epcylon ... (peut-être que cette discussion appartient au chat?)
Kobi
1
Un autre cas d'utilisation pratique consiste à trouver des expressions JSON dans une chaîne plus grande. Si vous voulez simplement demander "cette chaîne est-elle un objet JSON", alors oui, une bibliothèque d'analyse JSON est probablement un meilleur outil. Mais il ne peut pas trouver d'objets JSON dans une structure plus grande pour vous.
Mark Amery
1
Ce n'est pas une réponse, mais vous pouvez utiliser cette partie de la bibliothèque JSON-js de Crockford . Il utilise 4 regex et les combine de manière intelligente.
imgx64

Réponses:

182

Oui, une validation regex complète est possible.

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 .

Vérification RFC4627 plus simple

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 + ')');
mario
la source
22
+1 Il y a tellement de mal dans le monde de la part de personnes qui ne comprennent tout simplement pas la syntaxe des regex et qui en abusent comme raison de les haïr :(
NikiC
8
@mario, je ne sais pas si vous pensez que je suis dans le département des opposants , mais je ne suis pas. Notez que votre déclaration "La plupart des implémentations modernes de regex autorisent des expressions régulières récursives" est très discutable. AFAIK, seuls Perl, PHP et .NET ont la capacité de définir des modèles récursifs. Je n'appellerais pas cela «la plupart».
Bart Kiers
3
@Bart: Oui, c'est à juste titre discutable. Plus ironiquement, les moteurs Javascript regex ne peuvent pas utiliser une telle expression régulière récursive pour vérifier JSON (ou seulement avec des solutions de contournement élaborées). Donc, si regex == posix regex, ce n'est pas une option. Il est néanmoins intéressant de constater que c'est faisable avec les implémentations contemporaines; même avec quelques cas d'utilisation pratiques. (Mais c'est vrai, libpcre n'est pas le moteur le plus répandu partout.) - Aussi pour mémoire: j'espérais un badge d'inversion synthétique, mais le fait que vous n'obteniez pas quelques votes positifs de train en marche empêche cela. : /
mario
4
Nan. J'étais après le badge populiste, pour lequel j'ai besoin de 20 voix mais toujours de 10 voix sur votre réponse. Donc, au contraire, les votes négatifs sur votre question ne sont pas à mon avantage pour cela.
mario
2
Eh bien, en regardant plus loin, cette expression rationnelle a de nombreux autres problèmes. Il correspond aux données JSON, mais certaines données non JSON correspondent également. Par exemple, le littéral unique falsecorrespond 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.
dolmen
31

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 \Aet des \Zsé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.

Hrant Khachatrian
la source
1
L'utilisation \dest dangereuse. Dans de nombreuses implémentations de regexp \dcorrespond à la définition Unicode d'un chiffre qui n'est pas seulement [0-9]mais inclut à la place des scripts alternatifs.
dolmen
@dolmen: vous avez peut-être raison, mais vous ne devriez pas modifier cela vous-même dans la question. Il suffit de l'ajouter en commentaire.
Dennis Haarbrink
Je pense que \dcela 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}#umais pas#\d#u
Hrant Khachatrian
@ hrant-khachatrian: ce n'est pas parce que vous n'avez pas utilisé le /udrapeau. JSON est encodé en UTF-8. Pour une expression rationnelle correcte, vous devez utiliser cet indicateur.
dolmen
1
@dolmen J'ai utilisé le umodificateur, 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
Hrant Khachatrian
14

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

Bart Kiers
la source
16
@all down votants: "regex n'est pas adapté pour le valider" ne signifie pas que certains moteurs de regex ne peuvent pas le faire (du moins, c'est ce que je voulais dire). Bien sûr, certaines implémentations de regex le peuvent , mais toute personne sensée utiliserait simplement un analyseur JSON. Tout comme si quelqu'un demande comment construire une maison complète avec seulement un marteau, je répondrais qu'un marteau ne convient pas pour le travail, vous auriez besoin d'une boîte à outils complète et de machines. Bien sûr, quelqu'un avec suffisamment d'endurance peut le faire avec juste le marteau.
Bart Kiers
1
Cela peut être un avertissement valable, mais il ne répond pas à la question . Regex n'est peut-être pas le bon outil, mais certaines personnes n'ont pas le choix. Nous sommes enfermés dans un produit fournisseur qui évalue la sortie d'un service pour vérifier son intégrité, et la seule option que le fournisseur propose pour une vérification de l'état personnalisée est un formulaire Web qui accepte une expression régulière. Le produit du fournisseur qui évalue l'état du service n'est pas sous le contrôle de mon équipe. Pour nous, évaluer JSON avec regex est maintenant une exigence, par conséquent, une réponse «inadapté» n'est pas viable. (Je n'ai toujours pas voté contre vous.)
John Deters
11

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.jsonc'est en fait correct (selon la note du manuel et la chaîne valide RFC-7159 est également un JSON valide). Le fichier fail18.jsonn'é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.jsonet 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.

Gino Pane
la source
Cela correspondra uniquement aux valeurs JSON (chaînes, booléens et nombres), ce qui n'est pas un objet / tableau JSON.
kowsikbabu le
4

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:

  1. La chaîne commence et se termine par []ou{}
    • [{\[]{1}...[}\]]{1}
  2. et
    1. Le caractère est un caractère de contrôle JSON autorisé (un seul)
      • ... [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]...
    2. ou L'ensemble des caractères contenus dans un""
      • ... ".*?"...

Tous ensemble: [{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}

Si la chaîne JSON contient des newlinecaractères, vous devez utiliser le singlelinecommutateur 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.

cjbarth
la source
1
L'expression régulière suggérée a un comportement de retour arrière horrible sur certains cas de test. Si vous essayez de l'exécuter sur '{"a": false, "b": true, "c": 100, "' ce json incomplet, il s'arrête. Exemple: regex101.com/r/Zzc6sz . Une solution simple serait : [{[] {1} ([,: {} [] 0-9. \ - + Eaeflnr-u \ n \ r \ t] | ". *?") + [}]] {1}
Toonijn
@Toonijn J'ai mis à jour pour refléter votre commentaire. Merci!
cjbarth
3

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
pmarreck
la source
L'utilisation de \ 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. Donc, à moins que le support Unicode dans Ruby ne soit toujours cassé, vous devez corriger l'expression rationnelle dans votre code.
dolmen
Autant que je sache, Ruby utilise PCRE dans lequel \ d ne correspond pas à TOUTES les définitions Unicode de «chiffre». Ou dites-vous qu'il devrait?
pmarreck
Sauf que ce n'est pas le cas. Faux positif: "\ x00", [True]. Faux négatif: "\ u0000", "\ n". S'accroche: "[{" ": [{" ": [{" ":" (répété 1000x).
nst
Pas trop difficile à ajouter comme cas de test, puis à modifier le code pour réussir. Comment faire pour ne pas faire sauter la pile avec une profondeur de 1000+ est une question entièrement différente, cependant ...
pmarreck
1

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

Mikaeru
la source
L'utilisation \dest dangereuse. Dans de nombreuses implémentations de regexp \dcorrespond à la définition Unicode d'un chiffre qui n'est pas seulement [0-9]mais inclut à la place des scripts alternatifs.
dolmen
Cela semble un peu étrange, que -0 soit un nombre valide mais la RFC 4627 le permet et votre expression régulière s'y conforme.
ceving
1

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.

user117529
la source
0

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!

à l'extérieur
la source
0

Regex qui valide JSON simple et non JSONArray

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)*\}$

exemples de données validées par ce JSON

{
"key":"string",
"key": 56,
"key":{
        "attr":"integer",
        "attr": 12
        },
"key":{
        "key":[
            {
                "attr": 4,
                "attr": "string"
            }
        ]
     }
}
Ravi Nandasana
la source
-3

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;
}
Jamie
la source