Ir para o conteúdo
Toova
Todas as Ferramentas

7 Truques JSON que Vão te Salvar Horas

Toova

Todo desenvolvedor JavaScript usa JSON.parse e JSON.stringify dezenas de vezes por semana. Mas a maioria para no básico — parsear respostas de API e serializar objetos para strings. A API tem muito mais a oferecer, e algumas das funcionalidades menos usadas resolvem problemas que de outra forma exigiriam bibliotecas de terceiros ou horas de debug.

Este guia cobre sete truques que vão além do básico. Cada um é imediatamente aplicável em codebases reais. Sem abstrações, sem exemplos inventados — são padrões que aparecem em sistemas de produção.

Você pode verificar e explorar qualquer um dos exemplos de código neste artigo usando o JSON Formatter da Toova, que valida e formata JSON diretamente no navegador.

1. Clone Profundo via Round-Trip JSON (e seus Limites)

O truque mais antigo do livro JavaScript: usar JSON.parse(JSON.stringify(obj)) para criar um clone profundo de um objeto simples.

const original = { a: 1, b: { c: 2 } };

// Abordagem ingênua — objetos profundamente aninhados ainda são compartilhados
const shallowCopy = { ...original }; // b ainda aponta para o mesmo objeto

// Clone profundo via JSON — cria uma cópia completamente independente
const deepClone = JSON.parse(JSON.stringify(original));

deepClone.b.c = 99;
console.log(original.b.c); // 2 — original intocado

Funciona porque serializar para string e parsear de volta cria uma árvore de objetos completamente nova, sem referências compartilhadas. É rápido, não requer dependências e está disponível desde o ES5.

A alternativa moderna para uso em memória é structuredClone():

// Alternativa moderna: structuredClone() — lida com mais tipos
// Disponível no Node.js 17+ e em todos os navegadores modernos
const clone = structuredClone(original);

Conheça os limites da abordagem JSON antes de depender dela:

// JSON.parse/stringify NÃO consegue lidar com:
const broken = {
  date: new Date(),      // vira string — perde o protótipo Date
  fn: () => 'hello',    // descartado silenciosamente
  undef: undefined,     // descartado silenciosamente
  inf: Infinity,        // vira null
  map: new Map(),       // vira {}
  cycle: null,          // referências circulares lançam erro
};

Se o seu objeto contém apenas dados simples — strings, números, booleanos, arrays e objetos simples aninhados — o round-trip JSON é seguro e rápido. Para qualquer outra coisa, prefira structuredClone() ou uma biblioteca dedicada.

2. Replacer Customizado para Filtrar e Mascarar

A maioria dos desenvolvedores sabe que JSON.stringify aceita um segundo argumento, mas raramente o usa. Esse segundo argumento é o replacer: pode ser um array de chaves a incluir, ou uma função que controla exatamente como cada valor é serializado.

Replacer como array — lista branca de chaves específicas:

const user = {
  id: 'u_001',
  name: 'Alice',
  password: 'hunter2',        // não deve aparecer em logs
  creditCard: '4111111111111111', // não deve aparecer em logs
  role: 'admin',
};

// Replacer como array: inclui apenas estas chaves
JSON.stringify(user, ['id', 'name', 'role']);
// '{"id":"u_001","name":"Alice","role":"admin"}'

Replacer como função — transforma ou oculta valores:

// Replacer como função: controle total sobre chave/valor
const masked = JSON.stringify(user, (key, value) => {
  if (key === 'password' || key === 'creditCard') return '[REDACTED]';
  if (key === '' ) return value; // objeto raiz — sempre retornar
  return value;
});
// '{"id":"u_001","name":"Alice","password":"[REDACTED]","creditCard":"[REDACTED]","role":"admin"}'

Uma versão mais sofisticada mascara valores com base em seu formato em vez do nome da chave:

// Replacer para mascaramento baseado no tipo
const sanitize = (key, value) => {
  if (typeof value === 'string' && value.match(/^4[0-9]{15}$/)) {
    return '****-****-****-' + value.slice(-4);
  }
  return value;
};

Essa técnica é indispensável em middleware de logging: você quer logs estruturados com contexto completo do objeto, mas certos campos nunca devem chegar a um agregador de logs. O replacer permite tratar isso na fronteira de serialização, em vez de espalhar a lógica de redação por todo o codebase.

3. Ordenar Chaves de Forma Determinística

A ordem das chaves de um objeto em JavaScript é a ordem de inserção (para chaves string). Dois objetos com as mesmas chaves, criados em ordens diferentes, produzem strings JSON diferentes, o que quebra verificações ingênuas de igualdade, chaves de cache e hashes de conteúdo.

function sortedStringify(obj) {
  return JSON.stringify(obj, Object.keys(obj).sort());
}

const a = { z: 1, a: 2, m: 3 };
const b = { a: 2, m: 3, z: 1 };

sortedStringify(a) === sortedStringify(b); // true — ordem das chaves normalizada

Para objetos profundamente aninhados, aplique a ordenação recursivamente:

// Ordenação recursiva de chaves para objetos aninhados
function sortKeys(value) {
  if (Array.isArray(value)) return value.map(sortKeys);
  if (value !== null && typeof value === 'object') {
    return Object.fromEntries(
      Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])
    );
  }
  return value;
}

const sorted = JSON.stringify(sortKeys(deepNested));

JSON ordenado é essencial quando você está:

  • Gerando chaves de cache a partir de corpos de requisição
  • Computando checksums ou assinaturas sobre payloads JSON
  • Comparando respostas de API em testes independentemente da ordem dos campos
  • Armazenando objetos de configuração onde a ordem não deve afetar a igualdade

Depois de ordenar e formatar, use a ferramenta de Text Diff para comparar duas strings JSON normalizadas e ver exatamente quais valores mudaram entre versões.

4. Lidando com BigInt Sem Perder Precisão

O tipo Number do JavaScript representa com segurança inteiros até 253 − 1. Para IDs gerados por sistemas distribuídos, valores financeiros em unidades menores, ou timestamps em nanossegundos, isso não é suficiente. BigInt cobre inteiros de precisão arbitrária, mas JSON.stringify não sabe o que fazer com eles.

const data = {
  amount: 9007199254740993n, // maior que Number.MAX_SAFE_INTEGER
};

// Isso lança: TypeError: Do not know how to serialize a BigInt
JSON.stringify(data); // ERRO

As soluções padrão:

// Solução 1: Converter para string com um replacer
JSON.stringify(data, (key, value) =>
  typeof value === 'bigint' ? value.toString() : value
);
// '{"amount":"9007199254740993"}'

// Solução 2: toJSON() no protótipo BigInt (monkey-patch — use com cautela)
BigInt.prototype.toJSON = function () { return this.toString(); };
JSON.stringify(data); // '{"amount":"9007199254740993"}'

No lado do parsing, uma função reviver pode restaurar valores BigInt a partir de sua representação em string:

// Reviver para restaurar BigInt no parse
const revived = JSON.parse('{"amount":"9007199254740993"}', (key, value) => {
  if (key === 'amount') return BigInt(value);
  return value;
});
console.log(typeof revived.amount); // 'bigint'

Mantenha a conversão BigInt para string em uma camada de serialização compartilhada para que seja aplicada de forma consistente. Deixar BigInts vazar para chamadas ad hoc de JSON.stringify nas bordas de um codebase leva a erros imprevisíveis que são difíceis de rastrear.

5. Detecção de Referência Circular

Uma referência circular ocorre quando um objeto contém uma referência a si mesmo ou a um ancestral no grafo de objetos. Elas são mais comuns do que você imagina: event emitters, nós DOM, fibras do React e entidades ORM frequentemente têm back-references.

// Exemplo de referência circular
const obj = { name: 'node' };
obj.self = obj; // obj referencia a si mesmo

JSON.stringify(obj); // lança: TypeError: Converting circular structure to JSON

Trate isso com um replacer customizado que rastreia objetos visitados:

// Tratador manual de referência circular
function safeStringify(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[Circular]';
      seen.add(value);
    }
    return value;
  });
}

safeStringify(obj); // '{"name":"node","self":"[Circular]"}'

O WeakSet é a estrutura de dados correta aqui: mantém referências de objeto sem impedir a coleta de lixo, e a busca é O(1). Esse padrão também funciona em middleware de logging onde você quer que erros degradem graciosamente em vez de lançar exceções durante a serialização.

Uma variação comum: em vez de marcar referências circulares como [Circular], substitua-as por uma string de caminho como [Circular: $.config.parent] para tornar a localização da referência explícita durante o debug.

6. Pretty-Print de Arrays em Linha Única

O terceiro argumento de JSON.stringify é o recuo. Passar 2 ou 4 expande tudo em múltiplas linhas, o que é ótimo para objetos mas pode ser verboso para arrays de primitivos como tags, IDs ou coordenadas.

const mixed = {
  title: 'Relatório',
  tags: ['json', 'api', 'debug'],
  config: { indent: 2, sortKeys: true },
  count: 42,
};

// Padrão: tudo em múltiplas linhas
JSON.stringify(mixed, null, 2);
// {
//   "title": "Relatório",
//   "tags": [
//     "json",
//     "api",
//     "debug"
//   ],
//   "config": {
//     "indent": 2,
//     "sortKeys": true
//   },
//   "count": 42
// }

Você pode pós-processar a saída para colapsar arrays simples em uma linha:

// Replacer customizado para arrays em linha única
function prettyMixed(obj) {
  const raw = JSON.stringify(obj, null, 2);
  // Colapsa arrays com apenas primitivos em uma linha
  return raw.replace(
    /\[\n\s+([\s\S]*?)\n\s+\]/g,
    (match, inner) => {
      const items = inner.split(',\n').map((s) => s.trim());
      if (items.every((s) => !/^[{\[]/.test(s))) {
        return '[' + items.join(', ') + ']';
      }
      return match;
    }
  );
}

O resultado: objetos permanecem em múltiplas linhas para legibilidade, arrays de primitivos colapsam para uma linha para compacidade. Esse é o formato usado em muitos arquivos de configuração e saídas de log estruturado onde humanos e máquinas precisam ler os mesmos dados. O JSON Formatter e o conversor JSON para YAML tratam esse colapso automaticamente.

7. Streaming de JSON Grande

Carregar um arquivo JSON grande inteiramente na memória antes de parsear é o erro de performance mais comum em pipelines de processamento JSON. Uma resposta JSON de 100 MB aloca pelo menos 100 MB de heap para a string bruta, e depois uma segunda alocação para o objeto parseado. Para arquivos acima de alguns megabytes, um parser de streaming processa os dados de forma incremental.

No Node.js com uma biblioteca de streaming:

// node --experimental-vm-modules (ou use uma lib de JSON streaming)
// Streaming nativo com a proposta JSON Source Map (Stage 3, 2026):

// Por enquanto, use uma biblioteca como @streamparser/json ou jsonstream2
import { createReadStream } from 'fs';
import parser from '@streamparser/json';

const jsonParser = new parser.JSONParser();
jsonParser.onValue = ({ value, key, parent }) => {
  if (key === 'id') {
    console.log('ID encontrado:', value);
  }
};

createReadStream('large.json').pipe(jsonParser);

No navegador usando a Fetch Streams API:

// Streaming no navegador com Fetch + decodificador de stream JSON:
const response = await fetch('/api/large-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  // Processa objetos JSON completos do buffer...
}

Streaming é mais útil em pipelines de processamento em batch, grandes endpoints de exportação e scripts de análise de logs. Para respostas de API típicas abaixo de 1 MB, o JSON.parse padrão é rápido o suficiente e muito mais simples. O limiar onde o streaming começa a valer a pena na prática fica em torno de 5–10 MB, dependendo da complexidade da estrutura parseada e do tamanho do heap do ambiente de destino.

Para explorar a estrutura de respostas JSON grandes antes de construir um parser de streaming, cole uma amostra no JSON Formatter para ver quais caminhos contêm os dados que você precisa, e depois converta para CSV ou YAML para análise adicional.

Juntando Tudo

Essas sete técnicas cobrem os cantos mais impactantes da API JSON:

  • Clone profundo via round-trip — rápido para dados simples, conheça os limites
  • Replacer — filtre chaves e mascare valores sensíveis na fronteira de serialização
  • Ordenação de chaves — saída determinística para chaves de cache, assinaturas e asserções de teste
  • Tratamento de BigInt — serialize para string, restaure no parse
  • Detecção de referência circular — replacer baseado em WeakSet para serialização segura
  • Pretty-print com arrays compactos — saída legível por humanos sem excesso de espaço em branco
  • Streaming de JSON grande — parsing incremental para arquivos grandes e respostas de API

Para documentação completa dos argumentos replacer e space do JSON.stringify, veja a referência JSON.stringify no MDN. Para a alternativa moderna de clone profundo, veja a documentação do structuredClone(), que cobre todos os tipos suportados e casos extremos.