Javascript

Trabalhando com JSON Já leu

10 min de leitura

Trabalhando com JSON
JSON está em absolutamente tudo. É o formato de dados mais usado na web — em APIs, arquivos de configuração, bancos de dados NoSQL, comunicação entre serviços, LocalStorage e muito mais. Você já o usou nos artigos anteri

JSON está em absolutamente tudo. É o formato de dados mais usado na web — em APIs, arquivos de configuração, bancos de dados NoSQL, comunicação entre serviços, LocalStorage e muito mais. Você já o usou nos artigos anteriores, mas de forma superficial.

Neste artigo vamos explorar o JSON com profundidade — suas regras, seus limites, casos especiais, técnicas avançadas e os erros mais comuns que desenvolvedores cometem ao trabalhar com ele.


O que é JSON?

JSONJavaScript Object Notation — é um formato de texto para representar dados estruturados. Criado por Douglas Crockford no início dos anos 2000, foi inspirado na sintaxe de objetos do JavaScript mas é independente de linguagem — praticamente toda linguagem de programação moderna sabe ler e escrever JSON.

{
  "nome": "Ana Paula",
  "idade": 28,
  "ativo": true,
  "endereco": {
    "rua": "Av. Paulista",
    "numero": 1000,
    "cidade": "São Paulo"
  },
  "habilidades": ["JavaScript", "React", "Node.js"],
  "foto": null
}

Regras do JSON — diferenças do objeto JavaScript

JSON parece um objeto JavaScript, mas tem regras mais rígidas:

// ── Objeto JavaScript ──────────────────────────────
const obj = {
  nome: "Ana",            // chave sem aspas — válido em JS
  'idade': 28,            // aspas simples — válido em JS
  ativo: true,
  saudar() {              // métodos — válido em JS
    return "olá";
  },
  indefinido: undefined,  // undefined — válido em JS
};

// ── JSON válido ────────────────────────────────────
// ✅ Chaves SEMPRE entre aspas duplas
// ✅ Strings SEMPRE entre aspas duplas
// ❌ Sem métodos (funções)
// ❌ Sem undefined
// ❌ Sem comentários
// ❌ Sem trailing comma (vírgula no último item)

const jsonValido = `{
  "nome": "Ana",
  "idade": 28,
  "ativo": true,
  "endereco": null
}`;

JSON.stringify() — convertendo para string

const usuario = {
  nome: "Carlos",
  idade: 32,
  email: "carlos@email.com",
  ativo: true,
};

// Básico
const jsonString = JSON.stringify(usuario);
console.log(jsonString);
// {"nome":"Carlos","idade":32,"email":"carlos@email.com","ativo":true}

// Com indentação — muito mais legível
const jsonFormatado = JSON.stringify(usuario, null, 2);
console.log(jsonFormatado);
/*
{
  "nome": "Carlos",
  "idade": 32,
  "email": "carlos@email.com",
  "ativo": true
}
*/

// Com indentação de tab
const jsonTab = JSON.stringify(usuario, null, "	");

JSON.parse() — convertendo para objeto

const jsonString = '{"nome":"Ana","idade":28,"ativo":true}';

const objeto = JSON.parse(jsonString);

console.log(objeto.nome);   // "Ana"
console.log(objeto.idade);  // 28 — número, não string!
console.log(objeto.ativo);  // true — boolean, não string!
console.log(typeof objeto.idade); // "number"

O que stringify inclui e o que ignora

Esse é um ponto crítico que causa bugs sutis:

const dados = {
  nome: "Pedro",
  idade: 25,
  saudar: function() { return "olá"; },  // ❌ ignorado
  indefinido: undefined,                  // ❌ ignorado
  simbolo: Symbol("id"),                  // ❌ ignorado
  data: new Date(),                       // ⚠️  convertido para string ISO
  regex: /padrão/g,                       // ⚠️  vira objeto vazio {}
  infinito: Infinity,                     // ⚠️  vira null
  naoNumero: NaN,                         // ⚠️  vira null
  nulo: null,                             // ✅ mantido como null
};

console.log(JSON.stringify(dados, null, 2));
/*
{
  "nome": "Pedro",
  "idade": 25,
  "data": "2025-01-15T10:30:00.000Z",
  "regex": {},
  "infinito": null,
  "naoNumero": null,
  "nulo": null
}
*/
// saudar, indefinido e simbolo desapareceram silenciosamente!

O replacer — filtrando e transformando

O segundo argumento do JSON.stringify pode ser uma função ou array para controlar o que é incluído:

const usuario = {
  id: 1,
  nome: "Lucia",
  email: "lucia@email.com",
  senha: "hashDaSenha123",    // não deve ser serializado!
  saldo: 5000,
  createdAt: new Date(),
};

// ── Array replacer — inclui apenas os campos listados
const jsonPublico = JSON.stringify(usuario, ["id", "nome", "email"], 2);
console.log(jsonPublico);
/*
{
  "id": 1,
  "nome": "Lucia",
  "email": "lucia@email.com"
}
*/

// ── Function replacer — controle total
const jsonCustom = JSON.stringify(usuario, (chave, valor) => {
  // Ignora campos sensíveis
  if (chave === "senha") return undefined;

  // Formata datas de forma diferente
  if (valor instanceof Date) {
    return valor.toLocaleDateString("pt-BR");
  }

  // Mascara o saldo
  if (chave === "saldo") {
    return "R$ ***";
  }

  return valor; // retorna normalmente
}, 2);

console.log(jsonCustom);
/*
{
  "id": 1,
  "nome": "Lucia",
  "email": "lucia@email.com",
  "saldo": "R$ ***",
  "createdAt": "15/01/2025"
}
*/

O reviver — transformando ao fazer parse

O segundo argumento do JSON.parse é uma função reviver que transforma valores ao deserializar:

const jsonString = `{
  "nome": "Rafael",
  "nascimento": "1995-03-20T00:00:00.000Z",
  "ultimoAcesso": "2025-01-15T14:30:00.000Z",
  "saldo": "1500.50"
}`;

const objeto = JSON.parse(jsonString, (chave, valor) => {
  // Converte strings de data de volta para objetos Date
  if (chave === "nascimento" || chave === "ultimoAcesso") {
    return new Date(valor);
  }

  // Converte saldo de volta para número
  if (chave === "saldo") {
    return parseFloat(valor);
  }

  return valor;
});

console.log(objeto.nascimento instanceof Date); // true
console.log(objeto.nascimento.getFullYear());   // 1995
console.log(objeto.saldo + 100);                // 1600.5 — número real

O problema das datas com JSON

Datas são o caso mais comum de perda de tipo com JSON:

const evento = {
  titulo: "Conferência JavaScript",
  data: new Date("2025-06-15"),
  duracao: 8 * 60 * 60 * 1000, // 8 horas em ms
};

// Stringify converte Date para string ISO
const json = JSON.stringify(evento);
console.log(json);
// {"titulo":"Conferência JavaScript","data":"2025-06-15T00:00:00.000Z","duracao":28800000}

// Parse não reconstrói o Date automaticamente
const recuperado = JSON.parse(json);
console.log(typeof recuperado.data);           // "string" — não é Date!
console.log(recuperado.data instanceof Date);  // false

// ✅ Soluções:

// 1. Reviver manual
const correto = JSON.parse(json, (chave, valor) => {
  if (chave === "data") return new Date(valor);
  return valor;
});
console.log(correto.data instanceof Date); // true

// 2. Reconstituir após o parse
const obj = JSON.parse(json);
obj.data = new Date(obj.data);

// 3. Guardar timestamp (número) em vez de Date
const eventoSeguro = {
  titulo: "Conferência JavaScript",
  dataTimestamp: new Date("2025-06-15").getTime(), // número — sobrevive ao JSON
};

Cópia profunda com JSON

Um truque popular para fazer deep clone de objetos simples:

const original = {
  nome: "Beatriz",
  endereco: {
    cidade: "Porto Alegre",
    estado: "RS",
  },
  hobbies: ["leitura", "programação"],
};

// ✅ Deep clone simples com JSON
const copia = JSON.parse(JSON.stringify(original));

// Modifica a cópia
copia.endereco.cidade = "Florianópolis";
copia.hobbies.push("ciclismo");

// Original intacto
console.log(original.endereco.cidade); // "Porto Alegre"
console.log(original.hobbies.length);  // 2

Mas esse truque tem limitações — perde funções, datas, undefined, NaN e referências circulares. Para casos complexos, use structuredClone():

// ✅ structuredClone — deep clone nativo e robusto (ES2022)
const copia = structuredClone(original);
// Preserva Dates, RegExp, Map, Set, ArrayBuffer...
// Lança erro em referências circulares

Referências circulares — o erro mais temido

const pessoa = { nome: "João" };
pessoa.amigo = pessoa; // referência circular!

// ❌ Isso lança um erro
JSON.stringify(pessoa);
// TypeError: Converting circular structure to JSON

// ✅ Solução com replacer
function stringifySeguro(obj) {
  const vistos = new WeakSet();

  return JSON.stringify(obj, (chave, valor) => {
    if (typeof valor === "object" && valor !== null) {
      if (vistos.has(valor)) {
        return "[Referência Circular]";
      }
      vistos.add(valor);
    }
    return valor;
  });
}

console.log(stringifySeguro(pessoa));
// {"nome":"João","amigo":"[Referência Circular]"}

JSON5 e outras variantes

O JSON padrão é rígido. Existe o JSON5 — uma extensão não oficial que permite:

// JSON5 — mais permissivo (não é padrão!)
{
  // Comentários são permitidos
  nome: "Ana",           // chaves sem aspas
  'idade': 28,           // aspas simples
  hobbies: [
    "JS",
    "Node",              // trailing comma permitida
  ],
  salario: 5_000,        // underscore em números
  infinito: Infinity,    // valores especiais
}

JSON5 não é suportado nativamente — precisa de uma biblioteca. Para arquivos de configuração (como tsconfig.json e .eslintrc), algumas ferramentas aceitam comentários — na prática é JSONC (JSON with Comments), não JSON5.


Trabalhando com JSON em APIs reais

Padrões comuns que você vai encontrar:

// ── Resposta típica de uma API REST ────────────────
const respostaAPI = {
  "success": true,
  "data": {
    "usuarios": [
      { "id": 1, "nome": "Ana" },
      { "id": 2, "nome": "Bruno" },
    ],
    "total": 2,
    "pagina": 1,
    "porPagina": 10
  },
  "meta": {
    "versao": "1.0",
    "timestamp": "2025-01-15T10:30:00Z"
  }
};

// Extraindo dados com desestruturação
const { data: { usuarios, total, pagina } } = respostaAPI;
console.log(`${total} usuários — página ${pagina}`);
usuarios.forEach(u => console.log(u.nome));

// ── Paginação ─────────────────────────────────────
async function buscarTodosUsuarios() {
  const todos = [];
  let pagina = 1;
  let temMais = true;

  while (temMais) {
    const response = await fetch(
      `https://api.exemplo.com/usuarios?pagina=${pagina}&por_pagina=10`
    );
    const { data } = await response.json();

    todos.push(...data.usuarios);

    temMais = data.usuarios.length === 10; // se veio menos, acabou
    pagina++;
  }

  return todos;
}

Validação de JSON recebido

Nunca confie cegamente em dados de uma API externa:

function validarUsuario(dados) {
  const erros = [];

  if (!dados || typeof dados !== "object") {
    throw new Error("Dados inválidos — esperado um objeto.");
  }

  if (!dados.id || typeof dados.id !== "number") {
    erros.push("id deve ser um número.");
  }

  if (!dados.nome || typeof dados.nome !== "string" || dados.nome.trim().length < 2) {
    erros.push("nome deve ser uma string com pelo menos 2 caracteres.");
  }

  if (!dados.email || !dados.email.includes("@")) {
    erros.push("email inválido.");
  }

  if (erros.length > 0) {
    throw new Error(`Dados inválidos:
- ${erros.join("
- ")}`);
  }

  return true;
}

async function buscarEValidar(id) {
  const response = await fetch(`https://api.exemplo.com/usuarios/${id}`);
  const dados = await response.json();

  try {
    validarUsuario(dados);
    return dados;
  } catch (erro) {
    console.error(`Resposta inválida da API: ${erro.message}`);
    throw erro;
  }
}

Utilitário completo para JSON

const Json = {
  // Parse seguro com valor padrão
  parseSafe(texto, padrao = null) {
    try {
      return JSON.parse(texto);
    } catch {
      return padrao;
    }
  },

  // Stringify com tratamento de circulares
  stringifySafe(obj, espacos = 0) {
    const vistos = new WeakSet();
    return JSON.stringify(obj, (chave, valor) => {
      if (typeof valor === "object" && valor !== null) {
        if (vistos.has(valor)) return "[Circular]";
        vistos.add(valor);
      }
      return valor;
    }, espacos);
  },

  // Deep clone
  clonar(obj) {
    return this.parseSafe(JSON.stringify(obj));
  },

  // Verificar se uma string é JSON válido
  eValido(texto) {
    try {
      JSON.parse(texto);
      return true;
    } catch {
      return false;
    }
  },

  // Mesclar objetos profundamente via JSON (para objetos simples)
  mesclar(...objetos) {
    return objetos.reduce((acc, obj) => ({
      ...acc,
      ...JSON.parse(JSON.stringify(obj)),
    }), {});
  },
};

// Uso
console.log(Json.eValido('{"nome": "Ana"}')); // true
console.log(Json.eValido("isso não é json")); // false

const clone = Json.clonar({ a: { b: { c: 42 } } });
console.log(clone.a.b.c); // 42

const seguro = Json.stringifySafe({ nome: "João", ref: null });
console.log(seguro); // {"nome":"João","ref":null}

Tarefa para você

Construa um sistema de exportação e importação de dados:

// 1. Crie um array de 5 produtos com:
//    { id, nome, preco, categoria, criadoEm: new Date(), ativo }

// 2. Exporte para JSON formatado, mas:
//    - Formata datas como "DD/MM/AAAA"
//    - Remove produtos inativos
//    - Mascara preços acima de R$1000 como "Preço sob consulta"

// 3. Importe de volta do JSON e:
//    - Reconstrói as datas como objetos Date
//    - Valida que cada produto tem id, nome e preco
//    - Lança erro descritivo se algum campo estiver faltando

// 4. Exiba um relatório:
//    - Total de produtos exportados/importados
//    - Categorias únicas encontradas
//    - Produto mais caro
//    - Data de criação mais antiga

Conclusão

Neste artigo você aprendeu:

  • As regras rígidas do JSON e como diferem do objeto JavaScript
  • JSON.stringify() com replacer e indentação
  • JSON.parse() com reviver para reconstituir tipos
  • O que stringify ignora silenciosamente — a armadilha das funções e undefined
  • O problema clássico das datas com JSON
  • Deep clone com JSON e a alternativa moderna structuredClone
  • Como lidar com referências circulares
  • Paginação e validação de respostas de APIs
  • Um utilitário completo e reutilizável para JSON

No próximo artigo vamos aprender sobre tratamento de erros em requisições — como lidar com timeouts, falhas de rede, erros de API e criar uma estratégia robusta de resiliência.


📌 Próximo artigo: Aula 24 — Tratamento de erros em requisições HTTP


📚 Fontes e Referências

  • MDN Web Docs — JSON: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/JSON
  • MDN Web Docs — JSON.stringify: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
  • MDN Web Docs — JSON.parse: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
  • MDN Web Docs — structuredClone: https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
  • JavaScript.info — JSON methods: https://javascript.info/json
  • JSON5 Spec: https://json5.org
  • Douglas Crockford — Introducing JSON: https://www.json.org/json-en.html
  • JavaScript: The Good Parts — Douglas Crockford (O'Reilly)
  • You Don't Know JS: Types & Grammar — Kyle Simpson: https://github.com/getify/You-Dont-Know-JS
Comentários

Mais em Javascript

Mini Projeto: Calculadora no Console
Mini Projeto: Calculadora no Console

Chegamos ao fim do primeiro m&oacute;dulo. Em nove artigos voc&ecirc; percorr...

Arrays: criando e manipulando listas
Arrays: criando e manipulando listas

At&eacute; agora trabalhamos com vari&aacute;veis que guardam um &uacute;nico...

ESLint e Prettier: código limpo e padronizado
ESLint e Prettier: código limpo e padronizado

Imagine entrar em um projeto onde cada arquivo tem um estilo diferente — aspa...