JSON: parse, stringify e boas práticas

1. Introdução ao JSON no ecossistema JavaScript

JSON (JavaScript Object Notation) é o formato de intercâmbio de dados mais utilizado no desenvolvimento web moderno. Sua simplicidade e compatibilidade com JavaScript o tornaram o padrão de fato para APIs REST, arquivos de configuração e armazenamento local.

Embora JSON se pareça com objetos JavaScript, existem diferenças fundamentais:

  • Aspas duplas obrigatórias: todas as chaves e strings devem usar aspas duplas
  • Tipos suportados: apenas strings, números, booleanos, null, arrays e objetos
  • Valores não suportados: undefined, funções, símbolos, Map, Set, Date (em formato nativo)
// Objeto JavaScript válido
const user = {
  name: 'João',
  age: 30,
  sayHello: () => console.log('Olá'),
  id: undefined
};

// JSON equivalente (inválido como objeto JS sem aspas nas chaves)
const userJSON = '{"name": "João", "age": 30}';

Casos de uso típicos incluem:
- Comunicação com APIs REST
- Arquivos de configuração (package.json, tsconfig.json)
- Armazenamento no localStorage/sessionStorage
- Serialização de dados para transmissão em rede

2. JSON.parse(): convertendo strings em dados utilizáveis

O método JSON.parse() converte uma string JSON em um objeto JavaScript. Sua sintaxe aceita dois parâmetros:

JSON.parse(text[, reviver])

Tratamento de erros com try/catch

Sempre envolva chamadas a JSON.parse() em blocos try/catch para evitar crashes com JSON inválido:

const dadosInseguros = '{"nome": "Maria", "idade": "25"}';

try {
  const usuario = JSON.parse(dadosInseguros);
  console.log(usuario.nome); // "Maria"
} catch (erro) {
  console.error('Falha ao parsear JSON:', erro.message);
}

Usando o parâmetro reviver

O parâmetro reviver permite transformar valores durante a conversão. É útil para converter strings de data em objetos Date:

const jsonComData = '{"nome": "Ana", "nascimento": "1990-05-15T10:30:00.000Z"}';

const pessoa = JSON.parse(jsonComData, (chave, valor) => {
  if (chave === 'nascimento') {
    return new Date(valor);
  }
  return valor;
});

console.log(pessoa.nascimento instanceof Date); // true
console.log(pessoa.nascimento.getFullYear()); // 1990

3. JSON.stringify(): serializando objetos para transmissão

JSON.stringify() converte um objeto JavaScript em uma string JSON. Aceita três parâmetros:

JSON.stringify(value[, replacer[, space]])

Controle com replacer

O parâmetro replacer pode ser uma função ou array para filtrar quais propriedades incluir:

const usuario = {
  id: 1,
  nome: 'Carlos',
  senha: '123456',
  email: 'carlos@email.com'
};

// Usando array para selecionar campos
const jsonSeguro = JSON.stringify(usuario, ['nome', 'email']);
console.log(jsonSeguro); // {"nome":"Carlos","email":"carlos@email.com"}

// Usando função para transformar dados
const jsonFormatado = JSON.stringify(usuario, (chave, valor) => {
  if (chave === 'senha') return undefined; // Remove campo sensível
  return valor;
});

Formatação com espaço

O parâmetro space adiciona indentação para tornar o JSON legível:

const dados = { nome: 'João', endereco: { cidade: 'SP', uf: 'SP' } };
console.log(JSON.stringify(dados, null, 2));
// {
//   "nome": "João",
//   "endereco": {
//     "cidade": "SP",
//     "uf": "SP"
//   }
// }

4. Boas práticas de segurança com JSON

Nunca use eval() para parsear JSON

eval() executa código arbitrário e pode levar a ataques de injeção:

// ❌ PERIGOSO - nunca faça isso
const dadosMaliciosos = 'console.log("Código executado!")';
eval('(' + dadosMaliciosos + ')'); // Executa código malicioso

// ✅ Correto - use JSON.parse
try {
  const dados = JSON.parse(dadosMaliciosos);
} catch (e) {
  console.error('JSON inválido');
}

Validar dados de fontes externas

Sempre valide dados recebidos de APIs ou formulários. Bibliotecas como Zod ou Joi ajudam:

import { z } from 'zod';

const schemaUsuario = z.object({
  nome: z.string().min(2).max(100),
  email: z.string().email(),
  idade: z.number().int().positive()
});

function processarDados(jsonString) {
  try {
    const dadosBrutos = JSON.parse(jsonString);
    const dadosValidados = schemaUsuario.parse(dadosBrutos);
    return dadosValidados;
  } catch (erro) {
    console.error('Dados inválidos:', erro.errors);
    return null;
  }
}

Cuidados com prototype pollution

Ao mesclar objetos, evite que chaves como __proto__ contaminem o protótipo:

// ❌ Perigoso - pode causar prototype pollution
function mergePerigoso(alvo, fonte) {
  for (const chave in fonte) {
    alvo[chave] = fonte[chave];
  }
}

// ✅ Seguro - use Object.assign ou spread operator
function mergeSeguro(alvo, fonte) {
  return { ...alvo, ...fonte };
}

// Ou verifique explicitamente
function mergeComValidacao(alvo, fonte) {
  for (const [chave, valor] of Object.entries(fonte)) {
    if (chave !== '__proto__' && chave !== 'constructor') {
      alvo[chave] = valor;
    }
  }
  return alvo;
}

5. Manipulação de dados complexos e edge cases

Serializando Date, RegExp, Map e Set

Tipos especiais não são serializados automaticamente:

const dadosComplexos = {
  data: new Date('2024-01-15'),
  regex: /[a-z]+/g,
  mapa: new Map([['chave', 'valor']]),
  conjunto: new Set([1, 2, 3])
};

// ❌ Comportamento padrão - perde informações
console.log(JSON.stringify(dadosComplexos));
// {"data":"2024-01-15T00:00:00.000Z","regex":{},"mapa":{},"conjunto":{}}

// ✅ Solução: implementar toJSON() ou usar replacer
dadosComplexos.mapa.toJSON = function() {
  return Object.fromEntries(this);
};

dadosComplexos.conjunto.toJSON = function() {
  return [...this];
};

console.log(JSON.stringify(dadosComplexos));
// {"data":"2024-01-15T00:00:00.000Z","regex":{},"mapa":{"chave":"valor"},"conjunto":[1,2,3]}

Lidando com referências circulares

Objetos com referências circulares causam erro:

const objA = { nome: 'A' };
const objB = { nome: 'B', ref: objA };
objA.ref = objB;

// ❌ TypeError: Converting circular structure to JSON
// JSON.stringify(objA);

// ✅ Solução: detectar e substituir referências
function stringifyComCirculares(obj) {
  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;
  });
}

console.log(stringifyComCirculares(objA));
// {"nome":"A","ref":{"nome":"B","ref":"[Circular]"}}

Tratamento de valores especiais

const valoresEspeciais = {
  indefinido: undefined,    // Será removido
  nulo: null,               // Mantido como null
  nan: NaN,                 // Convertido para null
  infinito: Infinity        // Convertido para null
};

console.log(JSON.stringify(valoresEspeciais));
// {"nulo":null,"nan":null,"infinito":null}

6. JSON no Node.js: leitura e escrita de arquivos

Lendo arquivos JSON

import fs from 'fs';
import path from 'path';

function lerArquivoJSON(caminho) {
  try {
    const dados = fs.readFileSync(path.resolve(caminho), 'utf-8');
    return JSON.parse(dados);
  } catch (erro) {
    if (erro.code === 'ENOENT') {
      console.error('Arquivo não encontrado:', caminho);
      return null;
    }
    console.error('Erro ao ler JSON:', erro.message);
    return null;
  }
}

const config = lerArquivoJSON('./config.json');

Escrevendo arquivos JSON

function salvarArquivoJSON(caminho, dados) {
  try {
    const jsonString = JSON.stringify(dados, null, 2);
    fs.writeFileSync(path.resolve(caminho), jsonString, 'utf-8');
    console.log('Arquivo salvo com sucesso');
  } catch (erro) {
    console.error('Erro ao salvar JSON:', erro.message);
  }
}

const usuario = {
  id: 1,
  nome: 'Maria',
  email: 'maria@email.com',
  createdAt: new Date().toISOString()
};

salvarArquivoJSON('./usuarios/1.json', usuario);

Validação com schema antes de salvar

import { z } from 'zod';
import fs from 'fs';

const schemaConfig = z.object({
  port: z.number().int().min(1024).max(65535),
  host: z.string().ip(),
  debug: z.boolean().optional()
});

function salvarConfigSegura(caminho, dados) {
  try {
    const validados = schemaConfig.parse(dados);
    fs.writeFileSync(caminho, JSON.stringify(validados, null, 2));
    return true;
  } catch (erro) {
    console.error('Configuração inválida:', erro.errors);
    return false;
  }
}

7. JSON no React: estado, props e armazenamento local

Parseando dados de APIs no useEffect

import { useState, useEffect } from 'react';

function ListaUsuarios() {
  const [usuarios, setUsuarios] = useState([]);
  const [erro, setErro] = useState(null);

  useEffect(() => {
    async function carregarDados() {
      try {
        const resposta = await fetch('https://api.exemplo.com/usuarios');
        const dadosJSON = await resposta.text();
        const dados = JSON.parse(dadosJSON);
        setUsuarios(dados);
      } catch (erro) {
        setErro('Falha ao carregar dados');
      }
    }

    carregarDados();
  }, []);

  if (erro) return <div>Erro: {erro}</div>;

  return (
    <ul>
      {usuarios.map(usuario => (
        <li key={usuario.id}>{usuario.nome}</li>
      ))}
    </ul>
  );
}

Salvando preferências no localStorage

import { useState, useEffect } from 'react';

function useTema() {
  const [tema, setTema] = useState(() => {
    // Carrega preferência salva
    try {
      const salvo = localStorage.getItem('tema');
      return salvo ? JSON.parse(salvo) : 'claro';
    } catch {
      return 'claro';
    }
  });

  useEffect(() => {
    // Salva preferência quando muda
    localStorage.setItem('tema', JSON.stringify(tema));
  }, [tema]);

  const alternarTema = () => {
    setTema(prev => prev === 'claro' ? 'escuro' : 'claro');
  };

  return { tema, alternarTema };
}

Memoização de objetos JSON grandes

import { useMemo } from 'react';

function Dashboard({ dadosBrutos }) {
  // Evita re-processamento desnecessário
  const dadosProcessados = useMemo(() => {
    try {
      const dados = JSON.parse(dadosBrutos);
      return dados.map(item => ({
        ...item,
        dataFormatada: new Date(item.data).toLocaleDateString()
      }));
    } catch {
      return [];
    }
  }, [dadosBrutos]);

  return (
    <div>
      {dadosProcessados.map(item => (
        <div key={item.id}>{item.nome} - {item.dataFormatada}</div>
      ))}
    </div>
  );
}

Lembre-se: JSON é onipresente no ecossistema JavaScript, mas requer cuidado com segurança, tipos especiais e validação de dados externos. Dominar essas práticas evita bugs sutis e vulnerabilidades em suas aplicações.

Referências