Regex per convalidare JSON


91

Sto cercando un Regex che mi consenta di convalidare JSON.

Sono molto nuovo in Regex e so abbastanza che l'analisi con Regex è un male, ma può essere usato per convalidare?


31
Perché preoccuparsi di una fase di convalida separata? La maggior parte dei linguaggi dispone di librerie JSON in grado di analizzare JSON e, se è in grado di analizzarlo, era valido. In caso contrario, te lo dirà la biblioteca.
Epcylon

Devi analizzare il testo per convalidarlo ...
Ken

3
@mario - Non lo so ... sono favorevole ad abusare di espressioni regolari ed estremamente solidale con la tua obiezione alla fallacia "regex deve corrispondere a regolari" - ma non su questioni pratiche relative al lavoro. La migliore risposta qui è davvero il commento di Epcylon ... (forse questa discussione appartiene alla chat?)
Kobi

1
Un altro caso d'uso pratico è trovare espressioni JSON all'interno di una stringa più grande. Se vuoi semplicemente chiedere "questa stringa qui è un oggetto JSON", allora sì, una libreria di analisi JSON è probabilmente uno strumento migliore. Ma non riesce a trovare oggetti JSON all'interno di una struttura più ampia per te.
Mark Amery

1
Questa non è una risposta, ma puoi usare questa parte della libreria JSON-js di Crockford . Usa 4 regex e le combina in modo intelligente.
imgx64

Risposte:


187

Sì, è possibile una convalida dell'espressione regolare completa.

La maggior parte delle moderne implementazioni di espressioni regolari consente espressioni regex ricorsive, che possono verificare una struttura serializzata JSON completa. La specifica di json.org lo rende abbastanza semplice.

$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   
';

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

Verifica RFC4627 più semplice

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

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

23
+1 C'è così tanto male nel mondo da parte di persone che semplicemente non conoscono la sintassi regex e lo
usano

8
@mario, non sono sicuro se pensi che io sia nel dipartimento degli oppositori , ma non lo sono. Nota che la tua affermazione "La maggior parte delle moderne implementazioni regex consente regepressioni ricorsive" è altamente discutibile. AFAIK, solo Perl, PHP e .NET hanno la capacità di definire modelli ricorsivi. Non lo chiamerei "la maggior parte".
Bart Kiers

3
@Bart: Sì, è giustamente discutibile. La cosa più ironica è che i motori di regex Javascript non possono utilizzare una tale regex ricorsiva per verificare JSON (o solo con soluzioni alternative elaborate). Quindi se regex == posix regex, non è un'opzione. È comunque interessante che sia fattibile con le implementazioni contemporanee; anche con pochi casi d'uso pratici. (Ma è vero, libpcre non è il motore prevalente ovunque.) - Anche per la cronaca: speravo in un badge di inversione sintetica, ma il tuo non ricevere alcuni voti positivi lo impedisce. : /
mario

4
No. Stavo cercando il distintivo populista, per il quale ho bisogno di 20 voti ma ancora 10 voti sulla tua risposta. Quindi, al contrario, i voti negativi sulla tua domanda non sono a mio vantaggio per questo.
mario

2
Bene, guardando oltre, questa regexp ha molti altri problemi. Corrisponde ai dati JSON, ma anche ad alcuni dati non JSON. Ad esempio, il singolo letterale falsecorrisponde mentre il valore JSON di livello superiore deve essere un array o un oggetto. Ha anche molti problemi nel set di caratteri consentito nelle stringhe o negli spazi.
dolmen

32

Sì, è un'idea sbagliata comune che le espressioni regolari possano corrispondere solo alle lingue normali . In effetti, le funzioni PCRE possono corrispondere molto di più rispetto ai linguaggi normali , possono abbinare anche alcuni linguaggi non privi di contesto! L'articolo di Wikipedia su RegExps ha una sezione speciale a riguardo.

JSON può essere riconosciuto utilizzando PCRE in diversi modi! @mario ha mostrato un'ottima soluzione utilizzando modelli secondari denominati e riferimenti a ritroso . Quindi ha notato che dovrebbe esserci una soluzione utilizzando modelli 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 \Ae \Zsequenze che non dovrebbero essere usate all'interno dei subpattern. (?1)riferimenti alla regexp contrassegnati dalle parentesi più esterne (questo è il motivo per cui il più esterno ( )non inizia con ?:). Quindi, la RegExp diventa lunga 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 il filejson_decode() funzione (proprio come ha notato @Epcylon). Se intendo utilizzare quel JSON (se convalidato), questo è il metodo migliore.


1
L'utilizzo \dè pericoloso. In molte implementazioni regexp \dcorrisponde alla definizione Unicode di una cifra che non è solo [0-9]ma include script alternativi.
dolmen

@dolmen: potresti avere ragione, ma non dovresti modificarlo da solo nella domanda. Dovrebbe essere sufficiente aggiungerlo come commento.
Dennis Haarbrink

Penso \dche non corrisponda ai numeri Unicode nell'implementazione PHP di PCRE. Ad esempio, il ٩simbolo (0x669 cifra nove arabo-indic) verrà abbinato utilizzando il modello #\p{Nd}#uma non#\d#u
Hrant Khachatrian

@ hrant-khachatrian: non lo fa perché non hai usato la /ubandiera. JSON è codificato in UTF-8. Per una corretta regexp dovresti usare quella bandiera.
dolmen

1
@dolmen Ho usato il umodificatore, per favore guarda di nuovo i pattern nel mio commento precedente :) Stringhe, numeri e booleani SONO correttamente abbinati al livello superiore. Puoi incollare la lunga espressione regolare qui quanetic.com/Regex e provare tu stesso
Hrant Khachatrian,

14

A causa della natura ricorsiva di JSON (nidificato {...}-s), regex non è adatto per convalidarlo. Certo, alcune espressioni regolari possono abbinare ricorsivamente i pattern * (e quindi possono 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 regex non supportano modelli ricorsivi. Tra i più diffusi linguaggi di programmazione, questi supportano modelli ricorsivi: Perl, .NET, PHP e Ruby 1.9.2



16
@tutti i votanti: "regex non è adatto a convalidarlo" non significa che alcuni motori di regex non possono farlo (almeno, questo è quello che intendevo). Certo, alcune implementazioni di espressioni regolari possono farlo , ma chiunque sano di mente userebbe semplicemente un parser JSON. Proprio come se qualcuno chiedesse come costruire una casa completa con solo un martello, risponderei che un martello non è adatto per il lavoro, avresti bisogno di un kit completo di attrezzi e macchinari. Certo, qualcuno con sufficiente resistenza può farlo solo con il martello.
Bart Kiers

1
Questo può essere un avviso valido, ma non risponde alla domanda . L'espressione regolare potrebbe non essere lo strumento corretto, ma alcune persone non hanno scelta. Siamo bloccati in un prodotto del fornitore che valuta l'output di un servizio per verificarne l'integrità e l'unica opzione fornita dal fornitore per il controllo dello stato personalizzato è un modulo Web che accetta un'espressione regolare. Il prodotto del fornitore che valuta lo stato del servizio non è sotto il controllo del mio team. Per noi la valutazione di JSON con regex è ormai un requisito, quindi una risposta di "inadatto" non è fattibile. (Continuo a non averti sottovalutato.)
John Deters

12

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 falliti (fail1.json, fail18.json, fail25.json, fail27. json).

Ho esaminato gli errori e ho scoperto che in fail1.jsonrealtà è corretto (secondo la nota del manuale e la stringa valida RFC-7159 è anche un JSON valido). Anche il file fail18.jsonnon era il caso, perché contiene effettivamente un JSON profondamente annidato corretto:

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

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

["  tab character   in  string  "]

e

["line
break"]

Entrambi contengono caratteri non validi. Quindi ho aggiornato il modello in questo modo (modello secondario di stringa aggiornato):

$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';

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


Questo corrisponderà solo ai valori JSON (stringhe, booleani e numeri), che non è un oggetto / matrice JSON.
kowsikbabu

4

Guardando la documentazione per JSON , sembra che la regex possa essere semplicemente di tre parti se l'obiettivo è solo quello di verificare l'idoneità:

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

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

Se la stringa JSON contiene newlinecaratteri, è necessario utilizzare l' singlelineopzione nella versione regex in modo che .corrisponda newline. Si noti che questo non fallirà su tutti i JSON non validi, ma fallirà se la struttura JSON di base non è valida, che è un modo semplice per eseguire una convalida di sanità di base prima di passarla a un parser.


1
La regex suggerita ha un comportamento terribile di backtracking su alcuni casi di test. Se provi a eseguirlo su "{" a ": false," b ": true," c ": 100," 'questo json incompleto, si ferma. Esempio: regex101.com/r/Zzc6sz . Una semplice correzione sarebbe : [{[] {1} ([,: {} [] 0-9. \ - + Eaeflnr-u \ n \ r \ t] | ". *?") + [}]] {1}
Toonijn

@Toonijn ho aggiornato per riflettere il tuo commento. Grazie!
cjbarth

Questa versione leggermente modificata di @cjbarth funziona perfettamente per il mio caso d'uso di trovare tutte le strutture simili a JSON nel testo (applicato globalmente a un file HTML nel mio caso):[{\[]{1}([,:{}\[\]0-9.\-+A-zr-u \n\r\t]|".*:?")+[}\]]{1}
C2BB

3

Ho creato un'implementazione 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
         (?<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

Usare \ d è pericoloso. In molte implementazioni regexp \ d corrisponde alla definizione Unicode di una cifra che non è solo [0-9] ma include invece script alternativi. Quindi, a meno che il supporto Unicode in Ruby non sia ancora rotto, devi correggere la regexp nel tuo codice.
dolmen

Per quanto ne so, Ruby usa PCRE in cui \ d non corrisponde a TUTTE le definizioni unicode di "cifra". O stai dicendo che dovrebbe?
pmarreck

Solo che non lo fa. Falso positivo: "\ x00", [Vero]. Falso negativo: "\ u0000", "\ n". Aspetta: "[{" ": [{" ": [{" ":" (ripetuto 1000 volte).
NST

Non troppo difficile da aggiungere come casi di test e quindi modificare il codice per passare. Come non far saltare la pila con una profondità di 1000+ è una questione completamente diversa, però ...
pmarreck

1

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+)?

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


L'utilizzo \dè pericoloso. In molte implementazioni regexp \dcorrisponde alla definizione Unicode di una cifra che non è solo [0-9]ma include script alternativi.
dolmen

Sembra un po 'strano che -0 sia un numero valido ma RFC 4627 lo consente e la tua espressione regolare è conforme ad esso.
Ceving

1

Una virgola finale in un array JSON ha causato il blocco del mio Perl 5.16, probabilmente perché continuava a tornare indietro. Ho dovuto aggiungere una direttiva di terminazione del backtrack:

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

In questo modo, una volta identificato un costrutto che non è "opzionale" ( *o ?), non dovrebbe tentare di tornare indietro per cercare di identificarlo come qualcos'altro.


0

Come è stato scritto sopra, se la lingua che usi ha una libreria JSON in dotazione, 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 del genere con FreeMarker) la seguente regex potrebbe almeno fornire una convalida di base (è scritta per PHP / PCRE per essere testabile / utilizzabile per più utenti). Non è così infallibile come la soluzione accettata, ma 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, 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)

se mi sono perso qualcosa che potrebbe rompere questo involontariamente, sono grato per i commenti!


0

Regex che convalida JSON semplice non JSONArray

convalida chiave (stringa): valore (stringa, intero, [{chiave: valore}, {chiave: valore}], {chiave: valore})

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

dati di esempio convalidati da questo JSON

{
"key":"string",
"key": 56,
"key":{
        "attr":"integer",
        "attr": 12
        },
"key":{
        "key":[
            {
                "attr": 4,
                "attr": "string"
            }
        ]
     }
}


-2

Mi rendo conto che questo è di oltre 6 anni fa. Tuttavia, penso che ci sia una soluzione che nessuno qui ha menzionato che è molto più semplice del regex

function isAJSON(string) {
    try {
        JSON.parse(string)  
    } catch(e) {
        if(e instanceof SyntaxError) return false;
    };  
    return true;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.