web-dev-qa-db-fra.com

Regex pour valider JSON

Je cherche un regex qui me permette de valider json.

Je suis très nouveau chez Regex et je sais assez que l'analyse avec Regex est mauvaise, mais peut-elle être utilisée pour valider?

79
Shard

Oui, une validation complète de regex est possible.

La plupart des implémentations modernes de regex permettent des expressions rationnelles récursives, qui permettent de vérifier une structure sérialisée JSON complète. Le 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 dans PHP avec le fonctions PCRE . Devrait fonctionner non modifié en Perl; et peut certainement être adapté pour d'autres langages. En outre, il réussit avec le Scénarios de test JSON .

Vérification RFC4627 plus simple

Une approche plus simple est la vérification de cohérence minimale spécifiée dans RFC4627, section 6 . Il s'agit toutefois simplement d'un test de sécurité et d'une précaution élémentaire de non-validité:

  var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
         text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
     eval('(' + text + ')');
168
mario

Oui, c'est une idée fausse commune que les expressions régulières ne peuvent correspondre que langues normales . En fait, les fonctions PCRE peuvent correspondre à beaucoup plus que les langages normaux , elles peuvent même correspondre à des langages non contextuels! article de Wikipedia sur RegExps a une section spéciale à ce sujet.

JSON peut être reconnu à l'aide de PCRE de plusieurs manières! @mario a montré une solution de qualité utilisant des sous-modèles nommés et références arrières . Puis il a noté qu’il devrait y avoir une solution utilisant motifs 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 (?1) Au lieu de (?R) Car ce dernier fait référence au motif entier , mais nous avons \A Et \Z Séquences qui ne doivent pas être utilisées dans les sous-modèles. (?1) Fait référence à l'expression rationnelle marquée par les parenthèses externes (c'est pourquoi le plus externe ( ) Ne commence pas par ?:). Donc, le RegExp devient 268 caractères :)

/\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. Dans PHP), je validerai la chaîne JSON en appelant la fonction json_decode() (comme @Epcylon l’a noté). Si je vais utilisez ce JSON (s’il est validé), c’est la meilleure méthode.

27

En raison de la nature récursive de JSON (imbriqué {...}- s), regex n'est pas adapté pour le valider. Bien sûr, certains goûts de regex peuvent correspondre récursivement aux motifs* (et peut donc correspondre au format JSON), mais les modèles obtenus sont horribles à regarder et ne doivent jamais être utilisés dans le code de production IMO!

* Attention cependant, de nombreuses implémentations regex ne pas supportent les motifs 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

13
Bart Kiers

J'ai essayé la réponse de @ mario, mais cela n'a pas fonctionné car j'ai téléchargé la suite de tests de JSON.org ( archive ) et il y avait 4 tests ont échoué (fail1.json, fail18.json, fail25.json, fail27.json).

J'ai enquêté sur les erreurs et découvert que fail1.json est en fait correct (selon la note du manuel et RFC-7159 chaîne valide est également un JSON valide). Fichier fail18.json n'était pas le cas non plus, car il contient en fait du 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 (chaîne sous-modèle mise à 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 légaux de json.org peuvent être réussis.

8
Gino Pane

J'ai créé une implémentation de la solution de Mario Ruby), 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
3
pmarreck

En regardant la documentation de JSON , il semble que la regex puisse tout simplement être composée de trois parties si l'objectif 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 caractères newline, vous devez utiliser le commutateur singleline de votre style regex afin que . Corresponde à newline. Notez que cela n'échouera pas sur tous les JSON incorrects, mais si la structure JSON de base n'est pas valide, ce qui constitue un moyen simple de valider une validation de base avant de la transmettre à un analyseur.

3
cjbarth

Une virgule de fin dans un tableau JSON a provoqué l’arrêt de mon Perl 5.16, probablement parce qu’il continuait à revenir en arrière. Je devais ajouter une directive de terminaison de retour arrière:

(?<json>   \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*Prune) \s* )
                                                                                   ^^^^^^^^

De cette façon, une fois qu’elle identifie une construction qui n’est pas 'optionnelle' (* ou ?), il ne devrait pas essayer de revenir en arrière pour essayer de l'identifier comme autre chose.

1
user117529

Pour "chaînes et nombres", je pense que l'expression rationnelle partielle pour les nombres:

-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?

devrait être à la place:

-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?

étant donné que la partie décimale du nombre est facultative, il est probablement plus sûr d’échapper au - symbole dans [+-] puisqu'il a une signification particulière entre crochets

1
Mikaeru

Comme indiqué ci-dessus, si le langage que vous utilisez contient une bibliothèque JSON, utilisez-le pour essayer de décoder la chaîne et intercepter l'exception/l'erreur en cas d'échec! Si le langage ne le permet pas (le seul cas avec FreeMarker), les expressions rationnelles suivantes pourraient au moins fournir une validation très basique (il est écrit pour que PHP/PCRE puisse être testé/utilisé par 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 romprait cela involontairement, je suis reconnaissant pour les commentaires!

0
exside