Regex para validar JSON

89

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?

Fragmento
fonte
31
Por que se preocupar com uma etapa de validação separada? A maioria das linguagens tem bibliotecas JSON que podem analisar JSON e, se pudessem, era válido. Caso contrário, a biblioteca o informará.
Epcylon,
Você precisa analisar o texto para validá-lo ...
Ken
3
@mario - Não sei ... Sou totalmente a favor de abusar da regex e sou extremamente simpático à sua objeção à falácia "regex deve corresponder ao regular" - mas não em questões práticas relacionadas ao trabalho. A melhor resposta aqui é realmente o comentário de Epcylon ... (talvez essa discussão pertença ao chat?)
Kobi
1
Outro caso de uso prático é encontrar expressões JSON dentro de uma seqüência maior. Se você simplesmente deseja perguntar "esta string aqui é um objeto JSON", então sim, uma biblioteca de análise JSON é provavelmente uma ferramenta melhor. Mas ele não consegue encontrar objetos JSON dentro de uma estrutura maior para você.
Mark Amery
1
Isso não é uma resposta, mas você pode usar esta parte da biblioteca JSON-js de Crockford . Ele usa 4 regexes e os combina de uma maneira inteligente.
imgx64

Respostas:

182

Sim, uma validação de regex completa é possível.

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 .

Verificação RFC4627 mais simples

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 + ')');
mario
fonte
22
+1 Há tanta coisa ruim no mundo de pessoas que simplesmente não entendem a sintaxe regex e
usam
8
@mario, não tenho certeza se você acha que estou no departamento-pessimista , mas não estou Observe que sua declaração "A maioria das implementações de regex modernas permite expressões regulares recursivas" é altamente discutível. AFAIK, apenas Perl, PHP e .NET têm a capacidade de definir padrões recursivos. Eu não chamaria isso de "mais".
Bart Kiers
3
@ Bart: Sim, isso é discutível. Ironicamente, os motores de regex Javascript não podem usar esse regex recursivo para verificar JSON (ou apenas com soluções alternativas elaboradas). Portanto, se regex == posix regex, não é uma opção. No entanto, é interessante que isso seja possível com as implementações contemporâneas; mesmo com poucos casos de uso práticos. (Mas é verdade, libpcre não é o motor predominante em todos os lugares.) - Também para constar: eu estava esperando por um emblema de reversão sintética, mas o fato de você não conseguir alguns upvotes de movimento impede isso. : /
mario
4
Não. Eu estava atrás do emblema populista, para o qual exijo 20 votos, mas ainda assim 10 votos em sua resposta. Portanto, pelo contrário, os votos negativos em sua pergunta não são benéficos para mim.
mario
2
Bem, olhando mais além, esta regexp tem muitos outros problemas. Corresponde aos dados JSON, mas alguns dados não JSON também. Por exemplo, o único literal falsecorresponde, 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.
dolmen
31

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 \Ae \Zsequê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.

Hrant Khachatrian
fonte
1
Usar \dé perigoso. Em muitas implementações regexp \dcorresponde à definição Unicode de um dígito que não é justo, [0-9]mas inclui scripts alternativos.
Dolmen de
@dolmen: você pode estar certo, mas não deve editar isso sozinho na pergunta. Basta adicioná-lo como um comentário deve ser suficiente.
Dennis Haarbrink
Eu acho \dque 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}#umas não#\d#u
Hrant Khachatrian
@ hrant-khachatrian: não porque você não usou o /usinalizador. JSON é codificado em UTF-8. Para uma expressão regular adequada, você deve usar esse sinalizador.
Dolmen de
1
@dolmen Eu usei o umodificador, 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
Hrant Khachatrian
14

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

Bart Kiers
fonte
16
@todos os eleitores: "regex não é adequado para validá-lo" não significa que certos motores de regex não possam fazer isso (pelo menos, foi isso que eu quis dizer). Claro, algumas implementações de regex podem , mas qualquer pessoa em sã consciência simplesmente usaria um analisador JSON. Assim como se alguém perguntasse como construir uma casa completa com apenas um martelo, eu responderia que um martelo não é adequado para o trabalho, você precisaria de um kit de ferramentas e maquinário completos. Claro, alguém com resistência suficiente pode fazer isso apenas com o martelo.
Bart Kiers
1
Este pode ser um aviso válido, mas não responde à pergunta . Regex pode não ser a ferramenta correta, mas algumas pessoas não têm escolha. Estamos presos a um produto de fornecedor que avalia a saída de um serviço para verificar sua integridade, e a única opção que o fornecedor fornece para verificação de integridade personalizada é um formulário da web que aceita um regex. O produto do fornecedor que avalia o status do serviço não está sob o controle da minha equipe. Para nós, avaliar JSON com regex passou a ser um requisito, portanto, uma resposta de "inadequado" não é viável. (Eu ainda não votei contra você.)
John Deters
11

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.jsontambém não era o caso, porque ele contém o JSON profundamente aninhado correto:

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

Então, dois arquivos restantes: fail25.jsone 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.

Gino Pane
fonte
Isso corresponderá apenas aos valores JSON (strings, booleanos e números) também, que não é um objeto / array JSON.
kowsikbabu
4

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:

  1. A string começa e termina com []ou{}
    • [{\[]{1}...[}\]]{1}
  2. e
    1. O caractere é um caractere de controle JSON permitido (apenas um)
      • ... [,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]...
    2. ou O conjunto de caracteres contidos em um""
      • ... ".*?"...

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

Se a string JSON contiver newlinecaracteres, você deve usar a singlelineopçã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.

cjbarth
fonte
1
A regex sugerida tem um comportamento de retrocesso terrível em certos casos de teste. Se você tentar executá-lo em '{"a": false, "b": true, "c": 100, "' este json incompleto, ele para. Exemplo: regex101.com/r/Zzc6sz . Uma solução simples seria : [{[] {1} ([,: {} [] 0-9. \ - + Eaeflnr-u \ n \ r \ t] | ". *?") + [}]] {1}
Toonijn
@Toonijn Eu atualizei para refletir seu comentário. Obrigado!
cjbarth
3

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
pmarreck
fonte
Usar \ d é perigoso. Em muitas implementações de regexp, \ d corresponde à definição Unicode de um dígito que não é apenas [0-9], mas inclui scripts alternativos. Portanto, a menos que o suporte Unicode em Ruby ainda esteja quebrado, você deve corrigir o regexp em seu código.
Dolmen de
Pelo que eu sei, Ruby usa PCRE em que \ d não corresponde a TODAS as definições de Unicode de "dígito". Ou você está dizendo que deveria?
pmarreck
Exceto que não. Falso positivo: "\ x00", [Verdadeiro]. Falso negativo: "\ u0000", "\ n". Aguenta: "[{" ": [{" ": [{" ":" (repetido 1000x).
nst
Não é muito difícil adicionar como casos de teste e, em seguida, ajustar o código para passar. Como fazer para não estourar a pilha com uma profundidade de 1000+ é uma questão totalmente diferente, embora ...
pmarreck
1

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

Mikaeru
fonte
Usar \dé perigoso. Em muitas implementações regexp \dcorresponde à definição Unicode de um dígito que não é justo, [0-9]mas inclui scripts alternativos.
Dolmen de
Parece um pouco estranho que -0 é um número válido, mas o RFC 4627 permite e sua expressão regular está em conformidade com ele.
ceving
1

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.

user117529
fonte
0

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!

lado externo
fonte
0

Regex que valida JSON simples, não JSONArray

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

dados de amostra que validam por este JSON

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

Sei que isso é de mais de 6 anos atrás. No entanto, acho que há uma solução que ninguém aqui mencionou que é muito mais fácil do que regexing

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