Regex para validar JSON

Estoy buscando un Regex que me permita validar json.

Soy muy nuevo en Regex y sé lo suficiente que el análisis con Regex es malo, pero ¿se puede usar para validar?

Sí, es posible una validación de expresiones regulares completa.

La mayoría de las implementaciones de expresiones regulares modernas permiten expresiones regulares recursivas, que pueden verificar una estructura serializada JSON completa. La especificación json.org lo hace bastante sencillo.

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

Funciona bastante bien en PHP con las funciones PCRE . Debería funcionar sin modificaciones en Perl; y ciertamente puede ser adaptado para otros idiomas. También tiene éxito con los casos de prueba JSON .

Verificación RFC4627 más simple

Un enfoque más simple es la verificación de consistencia mínima como se especifica en RFC4627, sección 6 . Sin embargo, está destinado como prueba de seguridad y precaución básica de no validez:

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

Sí, es un concepto erróneo común que las expresiones regulares solo pueden coincidir con los idiomas normales . De hecho, las funciones de PCRE pueden coincidir mucho más que los lenguajes regulares , ¡incluso pueden coincidir con algunos idiomas sin contexto! El artículo de Wikipedia sobre RegExps tiene una sección especial al respecto.

¡JSON puede ser reconocido usando PCRE de varias maneras! @mario mostró una gran solución usando subpatrones y referencias secundarias nombradas. Luego notó que debería haber una solución usando patrones recursivos (?R) . Aquí hay un ejemplo de dicha expresión regular escrita 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'; 

Estoy usando (?1) lugar de (?R) porque este último hace referencia al patrón completo , pero tenemos secuencias \A y \Z que no deberían usarse dentro de los subpatrones. (?1) referencias a la expresión regular marcada por el paréntesis más externo (esta es la razón por la cual el más externo ( ) no comienza con ?: . Entonces, el RegExp se convierte en 268 caracteres de largo 🙂

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

De todos modos, esto debe ser tratado como una “demostración de tecnología”, no como una solución práctica. En PHP voy a validar la cadena JSON con la llamada a la función json_decode() (como anotó @Epcylon). Si voy a usar ese JSON (si está validado), este es el mejor método.

Debido a la naturaleza recursiva de JSON ( {...} -s nesteds), regex no es adecuada para validarlo. Claro, algunos sabores regex pueden coincidir recursivamente con los patrones * (y por lo tanto pueden coincidir con JSON), pero los patrones resultantes son horribles de mirar, ¡y nunca deberían usarse en el código de producción IMO!

* Sin embargo, ten cuidado, muchas implementaciones de expresiones regulares no admiten patrones recursivos. De los populares lenguajes de progtwigción, estos soportan patrones recursivos: Perl, .NET, PHP y Ruby 1.9.2

Intenté la respuesta de @mario, pero no funcionó, porque descargué el banco de pruebas de JSON.org ( archivo ) y hubo 4 pruebas fallidas (fail1.json, fail18.json, fail25.json, fail27. json).

Investigué los errores y descubrí que fail1.json es en realidad correcto (de acuerdo con la nota del manual y la cadena válida RFC-7159 también es un JSON válido). El archivo fail18.json tampoco fue el caso, porque contiene realmente JSON profundamente nested:

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

Entonces quedan dos archivos: fail25.json y fail27.json :

 [" tab character in string "] 

y

 ["line break"] 

Ambos contienen caracteres inválidos. Así que actualicé el patrón de esta manera (subpattern de cadena actualizado):

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

Entonces ahora se pueden pasar todas las pruebas legales de json.org .

Creé una implementación de Ruby de la solución de Mario, que funciona:

 # 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 

Para “cadenas y números”, creo que la expresión regular parcial para los números:

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

debería ser en su lugar:

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

ya que la parte decimal del número es opcional, y también es probablemente más seguro escapar del símbolo - en [+-] ya que tiene un significado especial entre paréntesis

Una coma que se arrastra en una matriz JSON provocó que mi Perl 5.16 se bloqueara, posiblemente porque se mantenía retrocediendo. Tuve que agregar una directiva de terminación de reversa:

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

De esta forma, una vez que identifica una construcción que no es ‘opcional’ ( * o ? ), No debe intentar retroceder sobre ella para intentar identificarla como algo más.

Mirando la documentación de JSON , parece que la expresión regular puede ser simplemente tres partes si el objective es solo verificar la aptitud:

  1. La cadena comienza y termina con [] o {}
    • [{\[]{1}[}\]]{1}
  2. y
    1. El personaje es un personaje de control JSON permitido (solo uno)
      • [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
    2. o El conjunto de caracteres contenidos en un ""
      • ".*?"

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

Si la cadena JSON contiene caracteres de newline , entonces debes usar el interruptor de una singleline en tu sabor de singleline regulares para que así sea . coincide con la newline . Tenga en cuenta que esto no fallará en todos los JSON incorrectos, pero fallará si la estructura básica de JSON no es válida, que es una forma directa de realizar una validación de cordura básica antes de pasarla a un analizador.

Como se describió anteriormente, si el lenguaje que utiliza tiene una biblioteca JSON que viene con él, úselo para intentar decodificar la cadena y atrapar la excepción / error si falla. Si el idioma no funciona (solo tenía un caso como este con FreeMarker) la siguiente expresión regular podría al menos proporcionar una validación muy básica (está escrita para que PHP / PCRE sea comprobable / utilizable para más usuarios). No es tan infalible como la solución aceptada, pero tampoco es tan aterradora =):

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

breve explicación:

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

si me perdí algo que rompería esto involuntariamente, ¡estoy agradecido por los comentarios!

Aquí mi expresión regular para validar cadena:

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

Se escribió con el diagtwig de syntax original .

Me doy cuenta de que esto es de hace más de 6 años. Sin embargo, creo que hay una solución que nadie ha mencionado aquí que sea mucho más fácil que la regexing

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