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?
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?
Respuestas:
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 .
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 + ')');
false
coincide, 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.
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 \A
y \Z
secuencias 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.
\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.
\d
que 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}#u
pero no#\d#u
/u
bandera. JSON está codificado en UTF-8. Para una expresión regular adecuada, debe usar esa bandera.
u
modificador, 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
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
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.json
es 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.json
tampoco fue el caso, porque contiene JSON profundamente anidado realmente correcto:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Así que quedan dos archivos: fail25.json
y 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 .
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:
[]
o{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Todos juntos:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Si la cadena JSON contiene newline
caracteres, entonces debe usar el singleline
interruptor 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.
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
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
\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.
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.
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!
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)*\}$
{
"key":"string",
"key": 56,
"key":{
"attr":"integer",
"attr": 12
},
"key":{
"key":[
{
"attr": 4,
"attr": "string"
}
]
}
}
Aquí mi expresión regular para validar la cadena:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Fue escrito usando el diagrama de sintaxis 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 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;
}