Regex per convalidare JSON

Sto cercando un Regex che mi permetta di convalidare JSON.

Sono molto nuovo a Regex e so abbastanza che l’analisi con Regex è ctriggers, ma può essere utilizzata per convalidare?

Sì, è ansible una convalida completa delle espressioni regolari.

La maggior parte delle implementazioni regex moderne consentono regexpressive ricorsive, che possono verificare una struttura serializzata JSON completa. Le specifiche di json.org lo rendono abbastanza semplice.

 $pcre_regex = ' / (?(DEFINE) (? -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (? true | false | null ) (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) (? \[ (?: (?&json) (?: , (?&json) )* )? \s* \] ) (? \s* (?&string) \s* : (?&json) ) (? \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) (? \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* ) ) \A (?&json) \Z /six '; 

Funziona abbastanza bene in PHP con le funzioni PCRE . Dovrebbe funzionare non modificato in Perl; e può certamente essere adattato per altre lingue. Inoltre riesce con i casi di test JSON .

Semplificazione della verifica RFC4627

Un approccio più semplice è il controllo della coerenza minimo come specificato nella RFC4627, sezione 6 . È tuttavia inteso solo come test di sicurezza e precauzione di non validità di base:

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

Sì, è un comune malinteso che le espressioni regolari possano corrispondere solo alle lingue regolari . In effetti, le funzioni PCRE possono corrispondere molto più delle normali lingue , possono anche abbinare linguaggi non contestuali! L’articolo di Wikipedia su RegExps ha una sezione speciale a riguardo.

JSON può essere riconosciuto utilizzando PCRE in diversi modi! @mario ha mostrato una soluzione eccezionale utilizzando sottoprotetti e riferimenti a posteriori nominati. Poi ha notato che dovrebbe esserci una soluzione usando schemi ricorsivi (?R) . Ecco un esempio di tale regexp scritto in 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'; 

Sto usando (?1) invece di (?R) perché quest’ultimo fa riferimento all’intero pattern, ma abbiamo sequenze \A e \Z che non dovrebbero essere usate all’interno di subpatterns. (?1) riferimenti all’espressione regolare contrassegnata dalle parentesi più esterne (questo è il motivo per cui il più esterno ( ) non inizia con ?: . Quindi, RegExp diventa lungo 268 caratteri 🙂

 /\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 

Ad ogni modo, questo dovrebbe essere trattato come una “dimostrazione tecnologica”, non come una soluzione pratica. In PHP convaliderò la stringa JSON chiamando la funzione json_decode() (proprio come notato da @Epcylon). Se ho intenzione di usare quel JSON (se è convalidato), allora questo è il metodo migliore.

A causa della natura ricorsiva di JSON (nidificato {...} -s), regex non è adatto per convalidarlo. Certo, alcuni sapori di espressioni regolari possono ricorsivamente corrispondere ai pattern * (e possono quindi corrispondere a JSON), ma i pattern risultanti sono orribili da guardare e non dovrebbero mai essere usati nel codice di produzione IMO!

* Attenzione però, molte implementazioni di espressioni regolari non supportano pattern ricorsivi. Tra i popolari linguaggi di programmazione, questi supportano pattern ricorsivi: Perl, .NET, PHP e Ruby 1.9.2

Ho provato la risposta di @ mario, ma non ha funzionato per me, perché ho scaricato la suite di test da JSON.org ( archivio ) e c’erano 4 test non riusciti (fail1.json, fail18.json, fail25.json, fail27. jSON).

Ho esaminato gli errori e ho scoperto che fail1.json è effettivamente corretto (secondo la nota del manuale e la stringa valida RFC-7159 è anche un JSON valido). Anche il file fail18.json non era il caso, poiché contiene JSON profondamente nidificati corretti:

 [[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] 

Quindi sono rimasti due file: fail25.json e fail27.json :

 [" tab character in string "] 

e

 ["line break"] 

Entrambi contengono caratteri non validi. Così ho aggiornato il modello come questo (sottotest delle stringhe aggiornato):

 $pcreRegex = '/ (?(DEFINE) (? -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (? true | false | null ) (? " ([^"\n\r\t\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) (? \[ (?: (?&json) (?: , (?&json) )* )? \s* \] ) (? \s* (?&string) \s* : (?&json) ) (? \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) (? \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) \s* ) ) \A (?&json) \Z /six'; 

Quindi ora tutti i test legali di json.org possono essere superati.

Ho creato un’implementazione di Ruby della soluzione di Mario, che funziona:

 # 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 (? -?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?){0} (? true | false | null ){0} (? " (?>[^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ){0} (? \[ (?> \g (?: , \g )* )? \s* \] ){0} (? \s* \g \s* : \g ){0} (? \{ (?> \g (?: , \g )* )? \s* \} ){0} (? \s* (?> \g | \g | \g | \g | \g ) \s* ){0} ) \A \g \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 

Per “stringhe e numeri”, penso che l’espressione regolare parziale per i numeri:

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

dovrebbe essere invece:

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

dato che la parte decimale del numero è facoltativa, ed è probabilmente più sicuro sfuggire al simbolo - in [+-] poiché ha un significato speciale tra parentesi

Una virgola finale in un array JSON ha causato il blocco del mio Perl 5.16, probabilmente perché manteneva il backtracking. Ho dovuto aggiungere una direttiva che terminava il backtrack:

 (? \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* ) ^^^^^^^^ 

In questo modo, una volta identificato un costrutto che non è “opzionale” ( * o ? ), Non dovrebbe provare a fare un backtracking per cercare di identificarlo come qualcos’altro.

Guardando la documentazione di JSON , sembra che la regex possa essere semplicemente composta da tre parti se l’objective è solo quello di verificare se è in forma:

  1. La stringa inizia e termina con [] o {}
    • [{\[]{1}[}\]]{1}
  2. e
    1. Il personaggio è un personaggio di controllo JSON consentito (solo uno)
      • [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
    2. o Il set di caratteri contenuto in un ""
      • ".*?"

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

Se la stringa JSON contiene caratteri di newline , allora dovresti usare l’interruttore di singleline sul tuo gusto regex in modo che . corrisponde a newline . Si prega di notare che questo non fallirà su tutti i JSON cattivi, ma fallirà se la struttura JSON di base non è valida, il che è un modo semplice per eseguire una validazione di base prima di passarla a un parser.

Come è stato scritto sopra, se la lingua che usi ha una libreria JSON che viene con essa, usala per provare a decodificare la stringa e catturare l’eccezione / errore se fallisce! Se il linguaggio non lo fa (ha appena avuto un caso simile con FreeMarker) la seguente espressione regolare potrebbe fornire almeno alcune validazioni di base (è scritto per PHP / PCRE per essere testabile / utilizzabile per più utenti). Non è infallibile come la soluzione accettata, ma non è nemmeno così spaventoso =):

 ~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s 

breve spiegazione:

 // we have two possibilities in case the string is JSON // 1. the string passed is "just" a JSON object, eg {"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, eg [{"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) 

se ho perso qualcosa che avrebbe rotto questo involontariamente, sono grato per i commenti!

Qui il mio regexp per validare la stringa:

 ^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$ 

È stato scritto usign syntax di syntax originale .

Mi rendo conto che questo è da oltre 6 anni fa. Tuttavia, penso che ci sia una soluzione che nessuno qui ha menzionato che è molto più facile della regexing

 function isAJSON(string) { try { JSON.parse(string) } catch(e) { if(e instanceof SyntaxError) return false; }; return true; }