Como comparar dois JSON ignorando a ordem das chaves

Dois JSON podem ter o mesmo conteúdo e textos diferentes: basta mudar a ordem das chaves ou os espaços. Por isso a comparação certa é feita nos dados, não no texto. Este guia mostra como listar as diferenças entre dois JSON em JavaScript e Python, como compará-los em PHP e as pegadinhas de tipo de cada linguagem, como true igual a 1.

Por Leonardo Gaertner · Publicado em

Por que não comparar o texto

{"a":1,"b":2} e { "b": 2, "a": 1 } têm o mesmo conteúdo, mas textos diferentes. Comparar com JSON.stringify(a) === JSON.stringify(b) também falha, porque a ordem das chaves se mantém no texto gerado. O caminho é ler os dois e comparar os valores, campo a campo.

Para ver as diferenças na hora, cole os dois no comparador de JSON, que lista o que foi adicionado, removido e alterado, com o caminho de cada campo.

As regras da comparação

  • Em objetos, a ordem das chaves não importa: o JSON não dá significado a ela.
  • Em listas, a ordem importa: [1, 2] e [2, 1] são diferentes. Se no seu caso a ordem não importar, ordene as duas listas antes (por um identificador, por exemplo).
  • Tipos contam: "1" (texto) é diferente de 1 (número), e true é diferente de 1.
  • null é diferente de uma chave ausente: {"a": null} e {} não são iguais.

Listar as diferenças em JavaScript

A função percorre os dois valores juntos e anota cada diferença com o caminho no estilo $.usuario.tags[1]. Para só saber se são iguais, no Node.js existe util.isDeepStrictEqual(a, b), que também ignora a ordem das chaves.

JavaScript
// Lista as diferenças entre dois valores JSON já lidos com JSON.parse.
// A ordem das chaves não conta; as listas são comparadas posição a posição.
function diffJSON(a, b, caminho = '$', saida = []) {
  const ehObjeto = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
  if (ehObjeto(a) && ehObjeto(b)) {
    for (const k of new Set([...Object.keys(a), ...Object.keys(b)])) {
      const p = `${caminho}.${k}`;
      // Object.hasOwn, e não "k in b": com in, uma chave toString existiria em todo objeto
      if (!Object.hasOwn(b, k)) saida.push({ caminho: p, tipo: 'removido', antes: a[k] });
      else if (!Object.hasOwn(a, k)) saida.push({ caminho: p, tipo: 'adicionado', depois: b[k] });
      else diffJSON(a[k], b[k], p, saida);
    }
  } else if (Array.isArray(a) && Array.isArray(b)) {
    for (let i = 0; i < Math.max(a.length, b.length); i++) {
      const p = `${caminho}[${i}]`;
      if (i >= b.length) saida.push({ caminho: p, tipo: 'removido', antes: a[i] });
      else if (i >= a.length) saida.push({ caminho: p, tipo: 'adicionado', depois: b[i] });
      else diffJSON(a[i], b[i], p, saida);
    }
  } else if (a !== b) {
    saida.push({ caminho, tipo: 'alterado', antes: a, depois: b });
  }
  return saida;
}

Em Python

Em Python, == entre dicionários já ignora a ordem das chaves. A pegadinha é que True == 1 também é verdadeiro, então {"a": true} e {"a": 1} pareceriam iguais. A função abaixo trata esse caso e devolve o mesmo resultado da versão em JavaScript.

Python
def _iguais(a, b) -> bool:
    # No Python, True == 1. No JSON, true e 1 são valores diferentes
    if isinstance(a, bool) or isinstance(b, bool):
        return type(a) is type(b) and a == b
    return a == b


def diff_json(a, b, caminho: str = "$", saida: list | None = None) -> list:
    """Diferenças entre dois valores lidos com json.loads (ordem das chaves não conta)."""
    saida = [] if saida is None else saida
    if isinstance(a, dict) and isinstance(b, dict):
        for k in list(a) + [k for k in b if k not in a]:
            p = f"{caminho}.{k}"
            if k not in b:
                saida.append({"caminho": p, "tipo": "removido", "antes": a[k]})
            elif k not in a:
                saida.append({"caminho": p, "tipo": "adicionado", "depois": b[k]})
            else:
                diff_json(a[k], b[k], p, saida)
    elif isinstance(a, list) and isinstance(b, list):
        for i in range(max(len(a), len(b))):
            p = f"{caminho}[{i}]"
            if i >= len(b):
                saida.append({"caminho": p, "tipo": "removido", "antes": a[i]})
            elif i >= len(a):
                saida.append({"caminho": p, "tipo": "adicionado", "depois": b[i]})
            else:
                diff_json(a[i], b[i], p, saida)
    elif not _iguais(a, b):
        saida.append({"caminho": caminho, "tipo": "alterado", "antes": a, "depois": b})
    return saida

Com 1 e 1.0, a função considera iguais, como o JavaScript.

Em PHP

No PHP, == compara arrays de forma frouxa, e "1" == 1 é verdadeiro. O === é estrito, mas exige as chaves na mesma ordem. A solução é ordenar as chaves dos objetos e então usar ===:

PHP
<?php
// Compara dois JSON pelo conteúdo, ignorando a ordem das chaves dos objetos
function jsonIguais(string $a, string $b): bool
{
    $ordenar = function ($v) use (&$ordenar) {
        if (!is_array($v)) {
            return $v;
        }
        $v = array_map($ordenar, $v);
        if (!array_is_list($v)) {
            ksort($v, SORT_STRING); // objeto: ordena as chaves; lista: mantém a ordem
        }
        return $v;
    };
    $va = $ordenar(json_decode($a, true, 512, JSON_THROW_ON_ERROR));
    $vb = $ordenar(json_decode($b, true, 512, JSON_THROW_ON_ERROR));
    return $va === $vb; // === e não ==: com ==, "1" seria igual a 1
}

Com json_decode em modo array, {} e [] viram o mesmo array vazio. Aqui 1 e 1.0 são diferentes.

Cuidados

  • Números grandes: no JavaScript, inteiros acima de 9007199254740991 perdem precisão no JSON.parse, e dois IDs diferentes podem parecer iguais. Trafegue IDs grandes como texto.
  • Datas em texto ("2026-01-01" e "2026-01-01T00:00:00Z") são textos diferentes, mesmo que representem o mesmo dia. Normalize antes, se for o caso.
  • Para aplicar as diferenças em outro lugar, existe um formato padrão: o JSON Patch (RFC 6902) descreve mudanças como operações de adicionar, remover e substituir.
  • Em testes automatizados, prefira a comparação profunda da biblioteca de testes (como toEqual no Jest e no Vitest), que mostra onde está a diferença.

Achou um erro neste guia? Avise por e-mail.

Perguntas frequentes

Como comparar dois JSON ignorando a ordem das chaves?

Leia os dois textos (JSON.parse, json.loads ou json_decode) e compare os valores, não o texto. Em Python, == entre dicionários já ignora a ordem. No JavaScript, use uma função de comparação profunda ou util.isDeepStrictEqual no Node.js.

A ordem dos itens de uma lista importa na comparação?

Sim. No JSON, uma lista é ordenada, então [1, 2] e [2, 1] são diferentes. Se a ordem não tiver significado no seu caso, ordene as duas listas antes de comparar.

Por que {"a": true} parece igual a {"a": 1} no Python?

Porque no Python o tipo bool é um tipo de número, e True == 1. Para comparar como o JSON, confira também se os dois valores são do mesmo tipo quando um deles for booleano.

Qual a diferença entre == e === ao comparar JSON no PHP?

O == é frouxo: considera "1" igual a 1 e ignora a ordem das chaves. O === é estrito nos tipos, mas exige a mesma ordem. Ordene as chaves e use ===.