É comum precisar comparar dados em JSON, como respostas de API, arquivos de configuração e arquivos de tradução. Mas, comparando dois JSON como texto puro, muitas vezes aparece um monte de diferenças mesmo com os dados iguais. Este guia explica por quê e como resolver.
Por que surgem falsas diferenças
O padrão JSON (RFC 8259) define o objeto como "uma coleção não ordenada de pares nome/valor". Ou seja, {"a":1,"b":2} e {"b":2,"a":1} representam os mesmos dados. Mas, como texto, a ordem das linhas é diferente, e o diff vê isso como diferença. Além disso, as seguintes diferenças aparecem sem nenhuma relação com os dados:
- Largura da indentação (2 espaços, 4 espaços, tabulação) e JSON compactado em uma linha
- Espaço ou não depois dos dois-pontos (
"a":1e"a": 1) - Notação de números (
1.0e1,1e2e100) - Escape de strings (
"\u00e9"e"é")
Solução: analisar e reescrever no mesmo formato
O método mais confiável é analisar (parse) os dois JSON, transformando-os em dados, e serializá-los de novo com as mesmas regras.
- Analise os dois textos como JSON. Se houver erro de sintaxe, corrija primeiro.
- Ordene recursivamente as chaves de todos os objetos.
- Reescreva com a mesma indentação (por exemplo, 2 espaços).
- Compare o resultado linha por linha.
Por exemplo, os dois JSON a seguir diferem na ordem das chaves e na ordem do array.
{"name":"kim","age":30,"tags":["a","b"]}
{"age":30,"name":"kim","tags":["b","a"]}
Depois de ordenar e reescrever, a diferença na ordem das chaves desaparece e só sobra a diferença real, a ordem do array.
@@ -2,7 +2,7 @@ "age": 30, "name": "kim", "tags": [- "a",- "b"+ "b",+ "a" ] }
Arrays não são ordenados
Ao contrário dos objetos, nos arrays a ordem tem significado. ["a","b"] e ["b","a"] são dados diferentes. Por isso, a regra é não ordenar arrays na normalização. Mas, se for um array em que a ordem não importa para o negócio, como uma lista de tags, ordená-lo você mesmo antes de comparar deixa o resultado mais fácil de ler. Qual é o certo depende do significado dos dados.
O que a análise acaba mudando
Analisar e reescrever tem efeitos colaterais que vale conhecer.
- A notação de números é unificada.
1.0e1viram1e parecem iguais. Na maioria dos casos isso é desejável, mas, quando a própria notação importa, compare também o texto original. - Precisão de inteiros grandes. O JavaScript trata números como ponto flutuante de 64 bits, então não representa com exatidão inteiros maiores que
Number.MAX_SAFE_INTEGER(2^53 − 1 = 9007199254740991). Por exemplo,9007199254740993vira9007199254740992depois da análise. Cuidado com JSON que guarda IDs longos como números. - Chaves duplicadas. A RFC 8259 apenas recomenda (SHOULD) que os nomes dentro de um objeto sejam únicos, e o tratamento de chaves duplicadas varia conforme a implementação. O
JSON.parsedo JavaScript fica com o último valor. - Comentários e vírgula final.
// comentárioe vírgulas finais como[1, 2,]não fazem parte do JSON padrão (são aceitos em formatos estendidos como JSON5 e JSONC). Um analisador padrão gera erro.
Pela linha de comando
Com o jq instalado, a opção -S (--sort-keys) imprime as chaves ordenadas.
jq -S . before.json > a.json
jq -S . after.json > b.json
diff -u a.json b.json
Diferença para o JSON Patch
O resultado do diff é uma lista de mudanças por linha, feita para pessoas lerem. Já o JSON Patch (RFC 6902) é um padrão que expressa mudanças pelo caminho na estrutura JSON, como {"op":"replace","path":"/age","value":31}, e o JSON Merge Patch (RFC 7396) é uma forma de sobrepor ao original um JSON que contém só as partes alteradas. Para programas trocarem mudanças entre si, esses padrões são os adequados; para revisão humana, o diff de texto depois da ordenação é o mais indicado.
Experimente nesta ferramenta
Ative Ordenar chaves JSON nas opções do comparador de textos: os dois lados são analisados, as chaves são ordenadas e o texto é reescrito com indentação de 2 espaços antes da comparação. A ordem dos arrays não é alterada, e, se a análise falhar, a ferramenta informa em qual lado e em qual caractere está o erro. Você também pode arrastar dois arquivos .json de uma vez. Para ler o resultado como patch, veja Como ler um unified diff.