Regex para validar JSON

90

Estoy buscando una expresión regular 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?

Casco
fuente
31
¿Por qué molestarse con un paso de validación separado? La mayoría de los lenguajes tienen bibliotecas JSON que pueden analizar JSON y, si puede analizarlo, es válido. Si no, la biblioteca se lo dirá.
Epcylon
Necesita analizar el texto para validarlo ...
Ken
3
@mario - No lo sé ... Soy partidario de abusar de las expresiones regulares, y simpatizo mucho con su objeción a la falacia de "la expresión regular debe coincidir con la normal", pero no en cuestiones prácticas relacionadas con el trabajo. La mejor respuesta aquí es realmente el comentario de Epcylon ... (¿tal vez esta discusión pertenece al chat?)
Kobi
1
Otro caso de uso práctico es encontrar expresiones JSON dentro de una cadena más grande. Si simplemente quiere preguntar "¿esta cadena aquí es un objeto JSON?", Entonces sí, una biblioteca de análisis JSON es probablemente una mejor herramienta. Pero no puede encontrar objetos JSON dentro de una estructura más grande para usted.
Mark Amery
1
Esta no es una respuesta, pero puede usar esta parte de la biblioteca JSON-js de Crockford . Utiliza 4 expresiones regulares y las combina de forma inteligente.
imgx64

Respuestas:

184

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

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

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

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

Verificación RFC4627 más sencilla

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

  var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
         text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
     eval('(' + text + ')');
mario
fuente
22
+1 Hay tantas cosas malas en el mundo de las personas que simplemente no entienden la sintaxis de
expresiones regulares
8
@mario, no estoy seguro de si crees que estoy en el departamento de detractores , pero no lo estoy. Tenga en cuenta que su declaración "La mayoría de las implementaciones modernas de expresiones regulares permiten expresiones regulares recursivas" es muy debatible. AFAIK, solo Perl, PHP y .NET tienen la capacidad de definir patrones recursivos. Yo no llamaría a eso "la mayoría".
Bart Kiers
3
@Bart: Sí, eso es justamente discutible. Irónicamente, los motores de expresiones regulares de Javascript no pueden usar una expresión regular recursiva para verificar JSON (o solo con soluciones elaboradas). Entonces, si regex == posix regex, no es una opción. No obstante, es interesante que sea factible con las implementaciones contemporáneas; incluso con pocos casos prácticos de uso. (Pero es cierto, libpcre no es el motor predominante en todas partes.) - También para que conste: esperaba una insignia de inversión sintética, pero el hecho de que no recibas algunos votos positivos lo impide. : /
mario
4
No Estaba detrás de la insignia populista, para la que necesito 20 votos, pero todavía 10 votos en su respuesta. Por el contrario, los votos negativos sobre su pregunta no me benefician por eso.
mario
2
Bueno, mirando más allá, esta expresión regular tiene muchos otros problemas. Coincide con datos JSON, pero también coincide con algunos datos que no son JSON. Por ejemplo, el literal único falsecoincide, mientras que el valor JSON de nivel superior debe ser una matriz o un objeto. También tiene muchos problemas en el juego de caracteres permitido en cadenas o espacios.
dolmen
32

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

JSON se puede reconocer usando PCRE de varias maneras. @mario mostró una gran solución utilizando subpatrones con nombre y referencias inversas . Luego señaló que debería haber una solución utilizando 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)en lugar de (?R)debido a las últimas referencias de la totalidad de patrón, pero tenemos \Ay \Zsecuencias que no deben utilizarse dentro de sub-patrones. (?1)referencias a la expresión regular marcada por el paréntesis más externo (es por eso que el más externo ( )no comienza con ?:). Entonces, la expresión regular se convierte en 268 caracteres :)

/\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 debería tratarse como una "demostración de tecnología", no como una solución práctica. En PHP, validaré la cadena JSON llamando a la json_decode()función (como señaló @Epcylon). Si voy a usar ese JSON (si está validado), entonces este es el mejor método.

Hrant Khachatrian
fuente
1
Usar \des peligroso. En muchas implementaciones de expresiones regulares \dcoincide con la definición Unicode de un dígito que no es solo [0-9]sino que incluye scripts alternativos.
dolmen
@dolmen: puede que tengas razón, pero no deberías editarlo tú mismo en la pregunta. Solo agregarlo como comentario debería ser suficiente.
Dennis Haarbrink
Creo \dque no coincide con los números Unicode en la implementación de PHP de PCRE. Por ejemplo ٩símbolo (0x669 dígitos árabe-Indic nueve) será igualado con el patrón #\p{Nd}#upero no#\d#u
Hrant Khachatrian
@ hrant-khachatrian: no lo hace porque no usó la /ubandera. JSON está codificado en UTF-8. Para una expresión regular adecuada, debe usar esa bandera.
dolmen
1
@dolmen Usé el umodificador, por favor mire nuevamente los patrones en mi comentario anterior :) Las cadenas, números y valores booleanos ESTÁN correctamente emparejados en el nivel superior. Puede pegar la expresión regular larga aquí quanetic.com/Regex y probarlo usted mismo
Hrant Khachatrian
14

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

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

Bart Kiers
fuente
16
@todos los votantes negativos: "la expresión regular no es adecuada para validarla" no significa que ciertos motores de expresiones regulares no puedan hacerlo (al menos, eso es lo que quise decir). Claro, algunas implementaciones de expresiones regulares pueden , pero cualquiera en su sano juicio simplemente usaría un analizador JSON. Al igual que si alguien pregunta cómo construir una casa completa con solo un martillo, respondería que un martillo no es adecuado para el trabajo, necesitaría un juego de herramientas y maquinaria completos. Claro, alguien con suficiente resistencia puede hacerlo con solo el martillo.
Bart Kiers
1
Esta puede ser una advertencia válida, pero no responde a la pregunta . Es posible que Regex no sea la herramienta correcta, pero algunas personas no tienen otra opción. Estamos encerrados en un producto de proveedor que evalúa el resultado de un servicio para verificar su estado, y la única opción que el proveedor ofrece para la verificación de estado personalizada es un formulario web que acepta una expresión regular. El producto del proveedor que evalúa el estado del servicio no está bajo el control de mi equipo. Para nosotros, evaluar JSON con expresiones regulares es ahora un requisito, por lo tanto, una respuesta de "inadecuado" no es viable. (Todavía no te voté en contra)
John Deters
12

Probé la respuesta de @ mario, pero no funcionó para mí, porque descargué el conjunto 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 eso fail1.jsones realmente 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.jsontampoco fue el caso, porque contiene JSON profundamente anidado realmente correcto:

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

Así que quedan dos archivos: fail25.jsony fail27.json:

["  tab character   in  string  "]

y

["line
break"]

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

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

Así que ahora se pueden pasar todas las pruebas legales de json.org .

Gino Pane
fuente
Esto coincidirá solo con los valores JSON (cadenas, valores booleanos y números) también, que no es un objeto / matriz JSON.
kowsikbabu
4

Al observar la documentación de JSON , parece que la expresión regular puede constar simplemente de tres partes si el objetivo es solo verificar la aptitud:

  1. La cadena comienza y termina con []o{}
    • [{\[]{1}...[}\]]{1}
  2. y
    1. El carácter es un carácter 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 newlinecaracteres, entonces debe usar el singlelineinterruptor en su sabor de expresión regular para que .coincida newline. Tenga en cuenta que esto no fallará en todos los JSON defectuosos, pero fallará si la estructura JSON básica no es válida, que es una forma sencilla de realizar una validación de cordura básica antes de pasarla a un analizador.

Cjbarth
fuente
1
La expresión regular sugerida tiene un comportamiento de retroceso terrible en ciertos casos de prueba. Si intentas ejecutarlo en '{"a": false, "b": true, "c": 100, "' este json incompleto, se detiene. Ejemplo: regex101.com/r/Zzc6sz . Una solución simple sería : [{[] {1} ([,: {} [] 0-9. \ - + Eaeflnr-u \ n \ r \ t] | ". *?") + [}]] {1}
Toonijn
@Toonijn He actualizado para reflejar su comentario. ¡Gracias!
cjbarth
3

Creé una implementación 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
         (?<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
fuente
Usar \ d es peligroso. En muchas implementaciones de expresiones regulares, \ d coincide con la definición Unicode de un dígito que no es solo [0-9] sino que incluye scripts alternativos. Entonces, a menos que el soporte Unicode en Ruby aún esté roto, debe corregir la expresión regular en su código.
dolmen
Hasta donde yo sé, Ruby usa PCRE en el que \ d no coincide con TODAS las definiciones Unicode de "dígito". ¿O estás diciendo que debería?
pmarreck
Excepto que no es así. Falso positivo: "\ x00", [Verdadero]. Falso negativo: "\ u0000", "\ n". Cuelga: "[{" ": [{" ": [{" ":" (repetido 1000x).
NST
No es demasiado difícil de agregar como casos de prueba y luego modificar el código para aprobar. Sin embargo, cómo hacer que no sople la pila con una profundidad de más de 1000 es un asunto completamente diferente ...
pmarreck
1

Para "cadenas y números", creo que la expresión regular parcial para 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 probablemente sea más seguro escapar del -símbolo [+-]ya que tiene un significado especial entre corchetes

Mikaeru
fuente
Usar \des peligroso. En muchas implementaciones de expresiones regulares \dcoincide con la definición Unicode de un dígito que no es solo [0-9]sino que incluye scripts alternativos.
dolmen
Parece un poco extraño, que -0 es un número válido pero RFC 4627 lo permite y su expresión regular se ajusta a él.
partir
1

Una coma final en una matriz JSON hizo que mi Perl 5.16 se bloqueara, posiblemente porque seguía retrocediendo. Tuve que agregar una directiva de terminación de backtrack:

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

De esta manera, una vez que identifica una construcción que no es 'opcional' ( *o ?), no debería intentar retroceder para tratar de identificarla como otra cosa.

usuario117529
fuente
0

Como se escribió anteriormente, si el lenguaje que usa tiene una biblioteca JSON, úsela para intentar decodificar la cadena y detectar la excepción / error si falla. Si el lenguaje no lo hace (solo tuvo un caso así con FreeMarker), la siguiente expresión regular podría al menos proporcionar una validación muy básica (está escrito para PHP / PCRE para ser comprobable / utilizable para más usuarios). No es tan infalible como la solución aceptada, pero tampoco es tan aterrador =):

~^\{\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, 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 me perdí algo que podría romper esto sin querer, ¡agradezco los comentarios!

exside
fuente
0

Regex que valida JSON simple, no JSONArray

valida clave (cadena): valor (cadena, entero, [{clave: valor}, {clave: valor}], {clave: valor})

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

datos de muestra que se validan con este JSON

{
"key":"string",
"key": 56,
"key":{
        "attr":"integer",
        "attr": 12
        },
"key":{
        "key":[
            {
                "attr": 4,
                "attr": "string"
            }
        ]
     }
}
Ravi Nandasana
fuente
-1

Aquí mi expresión regular para validar la cadena:

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

Fue escrito usando el diagrama de sintaxis original .

Sergey Kamardin
fuente
-3

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 aquí ha mencionado que es mucho más fácil que la expresión regular

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