Estou procurando um Regex que me permite validar json.
Eu sou muito novo no Regex e sei o suficiente que analisar com Regex é ruim, mas pode ser usado para validar?
Estou procurando um Regex que me permite validar json.
Eu sou muito novo no Regex e sei o suficiente que analisar com Regex é ruim, mas pode ser usado para validar?
Respostas:
A maioria das implementações de regex modernas permitem expressões regulares recursivas, que podem verificar uma estrutura serializada JSON completa. A especificação json.org torna-o bastante simples.
$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 muito bem em PHP com as funções PCRE . Deve funcionar sem modificações em Perl; e certamente pode ser adaptado para outros idiomas. Também tem sucesso com os casos de teste JSON .
Uma abordagem mais simples é a verificação de consistência mínima, conforme especificado no RFC4627, seção 6 . No entanto, serve apenas como teste de segurança e precaução básica de não validade:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + text + ')');
false
corresponde, enquanto o valor JSON de nível superior deve ser uma matriz ou um objeto. Ele também tem muitos problemas no conjunto de caracteres permitidos em strings ou espaços.
Sim, é um equívoco comum pensar que expressões regulares podem corresponder apenas a idiomas regulares . Na verdade, as funções PCRE podem corresponder a muito mais do que linguagens regulares , elas podem corresponder até mesmo a algumas linguagens não livres de contexto! O artigo da Wikipedia sobre RegExps tem uma seção especial sobre isso.
JSON pode ser reconhecido usando PCRE de várias maneiras! @mario mostrou uma ótima solução usando subpadrões nomeados e referências anteriores . Em seguida, ele observou que deveria haver uma solução usando padrões recursivos (?R)
. Aqui está um exemplo de tal regexp escrito em 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';
Estou usando em (?1)
vez de (?R)
porque o último faz referência a todo o padrão, mas temos \A
e \Z
sequências que não devem ser usadas dentro de subpadrões. (?1)
referências ao regexp marcado pelos parênteses externos (é por isso que o externo ( )
não começa com ?:
). Portanto, o RegExp passa a ter 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 qualquer forma, isso deve ser tratado como uma "demonstração de tecnologia", não como uma solução prática. Em PHP, validarei a string JSON chamando a json_decode()
função (assim como @Epcylon observou). Se for usar esse JSON (se estiver validado), esse é o melhor método.
\d
é perigoso. Em muitas implementações regexp \d
corresponde à definição Unicode de um dígito que não é justo, [0-9]
mas inclui scripts alternativos.
\d
que não corresponde aos números Unicode na implementação do PCRE do PHP. Por exemplo, o ٩
símbolo (0x669 dígito indicativo-árabe nove) será combinado usando o padrão, #\p{Nd}#u
mas não#\d#u
/u
sinalizador. JSON é codificado em UTF-8. Para uma expressão regular adequada, você deve usar esse sinalizador.
u
modificador, por favor, olhe novamente para os padrões em meu comentário anterior :) Strings, números e booleanos SÃO correspondidos corretamente no nível superior. Você pode colar o regexp longo aqui quanetic.com/Regex e experimentar
Devido à natureza recursiva do JSON (aninhado {...}
-s), regex não é adequado para validá-lo. Claro, alguns tipos de regex podem corresponder recursivamente a padrões * (e, portanto, podem corresponder a JSON), mas os padrões resultantes são horríveis de se olhar e nunca devem ser usados no código de produção IMO!
* Porém, cuidado, muitas implementações de regex não suportam padrões recursivos. Das linguagens de programação populares, essas suportam padrões recursivos: Perl, .NET, PHP e Ruby 1.9.2
Eu tentei a resposta de @mario, mas não funcionou para mim, porque eu baixei o conjunto de testes do JSON.org ( arquivo ) e houve 4 testes com falha (fail1.json, fail18.json, fail25.json, fail27. json).
Eu investiguei os erros e descobri que fail1.json
está correto (de acordo com a nota do manual e a string válida RFC-7159 também é um JSON válido). O arquivo fail18.json
também não era o caso, porque ele contém o JSON profundamente aninhado correto:
[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
Então, dois arquivos restantes: fail25.json
e fail27.json
:
[" tab character in string "]
e
["line
break"]
Ambos contêm caracteres inválidos. Então, eu atualizei o padrão assim (subpadrão de string atualizado):
$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';
Então agora todos os testes legais de json.org podem ser aprovados.
Olhando a documentação para JSON , parece que a regex pode ser simplesmente três partes se o objetivo for apenas verificar a adequação:
[]
ou{}
[{\[]{1}
...[}\]]{1}
[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]
...""
".*?"
...Todos juntos:
[{\[]{1}([,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]|".*?")+[}\]]{1}
Se a string JSON contiver newline
caracteres, você deve usar a singleline
opção no seu tipo de regex para que .
corresponda newline
. Observe que isso não falhará em todos os JSON inválidos, mas falhará se a estrutura JSON básica for inválida, que é uma maneira direta de fazer uma validação de sanidade básica antes de passá-la para um analisador.
Eu criei uma implementação Ruby da solução 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 "strings e números", acho que a expressão regular parcial para números:
-?(?:0|[1-9]\d*)(?:\.\d+)(?:[eE][+-]\d+)?
deveria ser em vez disso:
-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?
uma vez que a parte decimal do número é opcional, e também é provavelmente mais seguro escapar do -
símbolo, [+-]
pois ele tem um significado especial entre colchetes
\d
é perigoso. Em muitas implementações regexp \d
corresponde à definição Unicode de um dígito que não é justo, [0-9]
mas inclui scripts alternativos.
Uma vírgula final em uma matriz JSON fez com que meu Perl 5.16 travasse, possivelmente porque ele continuava retrocedendo. Tive que adicionar uma diretiva de encerramento de retrocesso:
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) )(*PRUNE) \s* )
^^^^^^^^
Dessa forma, uma vez que identifica uma construção que não é 'opcional' ( *
ou ?
), não deve tentar retrocedê-la para tentar identificá-la como outra coisa.
Como foi escrito acima, se a linguagem que você usa tem uma biblioteca JSON incluída, use-a para tentar decodificar a string e capturar a exceção / erro se ela falhar! Se a linguagem não (apenas teve esse caso com FreeMarker), o regex a seguir poderia pelo menos fornecer alguma validação muito básica (é escrito para PHP / PCRE para ser testável / utilizável para mais usuários). Não é tão infalível quanto a solução aceita, mas também não é tão assustador =):
~^\{\s*\".*\}$|^\[\n?\{\s*\".*\}\n?\]$~s
breve explicação:
// 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 eu perdi algo que quebraria isso sem querer, agradeço os comentários!
ele valida chave (string): valor (string, inteiro, [{chave: valor}, {chave: valor}], {chave: 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"
}
]
}
}
Aqui, meu regexp para validar string:
^\"([^\"\\]*|\\(["\\\/bfnrt]{1}|u[a-f0-9]{4}))*\"$
Foi escrito usando o diagrama de sintaxe original .