JSON (JavaScript Object Notation) é um formato leve de troca de dados, fácil de ler e escrever para humanos e fácil de interpretar e gerar para máquinas. Embora seja derivado da sintaxe de objetos JavaScript, é independente de linguagem e amplamente utilizado em APIs web, arquivos de configuração e armazenamento de dados.

Nesta aula, vamos focar no uso prático de JSON em JavaScript: como converter entre objetos JavaScript e strings JSON, evitar erros comuns e consumir dados de APIs reais.

O que é JSON

JSON é uma string que representa dados estruturados no formato de pares chave-valor e listas ordenadas. Ele suporta tipos primitivos: string, número, booleano, null, objeto e array. Diferente de objetos JavaScript, as chaves devem estar entre aspas duplas, e não são permitidos comentários ou funções.

Exemplo de JSON válido:

{
  "nome": "Ana",
  "idade": 28,
  "ativo": true,
  "habilidades": ["JS", "Python"],
  "endereco": null
}

Observe que as strings usam aspas duplas, não simples. Isso é obrigatório no JSON. Além disso, números não têm aspas, booleanos são escritos em minúsculo e null é a palavra reservada.

JSON.parse e JSON.stringify

Para trabalhar com JSON em JavaScript, usamos dois métodos estáticos do objeto global JSON:

  • JSON.parse(): converte uma string JSON em um valor JavaScript (objeto, array, etc.).
  • JSON.stringify(): converte um valor JavaScript em uma string JSON.

Exemplo prático:

// String JSON
const jsonString = '{"nome":"João","idade":30}';

// Converter para objeto
const pessoa = JSON.parse(jsonString);
console.log(pessoa.nome); // "João"

// Voltar para string
const novaString = JSON.stringify(pessoa);
console.log(novaString); // '{"nome":"João","idade":30}'

JSON.parse também aceita um segundo parâmetro opcional, um reviver (função de transformação) que pode modificar os valores durante a conversão. Já JSON.stringify aceita um replacer (função ou array para filtrar chaves) e um space (número ou string para formatação com indentação).

// Usando reviver para converter datas
const jsonComData = '{"data":"2023-12-25T10:00:00Z"}';
const obj = JSON.parse(jsonComData, (key, value) => {
  if (key === 'data') return new Date(value);
  return value;
});
console.log(obj.data instanceof Date); // true

// Usando replacer e space
const filtrado = JSON.stringify(pessoa, ['nome'], 2);
console.log(filtrado);
// '{
//   "nome": "João"
// }'

Pegadinhas

Ao trabalhar com JSON, alguns erros são comuns:

  • Aspas simples ou sem aspas: JSON exige aspas duplas nas chaves e strings. Usar aspas simples ou omitir aspas causa erro de sintaxe.
  • Vírgula sobrando: JSON não permite vírgula após o último par ou elemento. Exemplo inválido: {"a":1,}.
  • Tipos não suportados: undefined, funções, símbolos, e datas (como objeto) não são suportados. JSON.stringify ignora undefined e funções, e converte datas para string ISO.
  • Objetos com referências circulares: JSON.stringify lança um erro se o objeto tiver referências circulares (ex: obj.self = obj).
  • Precisão numérica: Números muito grandes ou com muitas casas decimais podem perder precisão. Use strings para números que exijam precisão exata (ex: IDs grandes).

Exemplo de erro de sintaxe:

// Incorreto - aspas simples
const invalido = "{'nome': 'Maria'}";
try {
  JSON.parse(invalido);
} catch (e) {
  console.log(e.message); // Unexpected token ' in JSON at position 1
}

// Correto
const valido = '{"nome":"Maria"}';
JSON.parse(valido); // funciona

Outra pegadinha: JSON.stringify pode produzir saídas inesperadas para certos valores:

console.log(JSON.stringify(undefined)); // undefined (não retorna string)
console.log(JSON.stringify([undefined])); // '[null]'
console.log(JSON.stringify({a: undefined})); // '{}'

Uso com APIs

Na prática, o JSON é o formato mais comum para comunicação com APIs REST. Usamos fetch para fazer requisições e processar a resposta como JSON. Exemplo de consumo de uma API pública:

// Buscar dados de usuários
fetch('https://jsonplaceholder.typicode.com/users/1')
  .then(response => {
    if (!response.ok) throw new Error('Erro na requisição');
    return response.json(); // retorna uma Promise que resolve para o objeto
  })
  .then(usuario => {
    console.log(usuario.name); // "Leanne Graham"
    console.log(usuario.email); // "Sincere@april.biz"
  })
  .catch(erro => console.error(erro));

Para enviar dados no corpo de uma requisição POST, usamos JSON.stringify e definimos o cabeçalho Content-Type como application/json:

const novoUsuario = {
  name: 'Maria',
  email: 'maria@example.com'
};

fetch('https://jsonplaceholder.typicode.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(novoUsuario)
})
  .then(res => res.json())
  .then(data => console.log('Criado:', data));

Lembre-se de sempre tratar erros: rede, status HTTP não-2xx, e JSON malformado. Use try/catch com async/await para um código mais limpo:

async function buscarUsuario(id) {
  try {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    if (!res.ok) throw new Error(`Status: ${res.status}`);
    const usuario = await res.json();
    return usuario;
  } catch (erro) {
    console.error('Falha ao buscar usuário:', erro);
    return null;
  }
}

buscarUsuario(1).then(u => console.log(u?.name));

Boas práticas

  • Sempre valide JSON recebido de fontes externas antes de usar.
  • Use JSON.parse com try/catch para evitar que erros quebrem a aplicação.
  • Ao criar APIs, retorne JSON com chaves consistentes (ex: snake_case ou camelCase, mas escolha um padrão).
  • Evite enviar dados sensíveis em JSON sem criptografia (use HTTPS).
  • Para objetos complexos, considere usar bibliotecas de serialização (ex: superjson para datas).

Referências

Exercícios

  1. Crie uma string JSON representando um livro com propriedades: título (string), autor (string), ano (número) e gêneros (array de strings). Converta essa string em um objeto JavaScript e exiba o título no console.

    ✓ Resposta:
    const jsonLivro = '{"titulo":"O Senhor dos Anéis","autor":"J.R.R. Tolkien","ano":1954,"generos":["Fantasia","Aventura"]}';
    const livro = JSON.parse(jsonLivro);
    console.log(livro.titulo); // "O Senhor dos Anéis"
  2. Dado o objeto const carro = { marca: 'Fiat', modelo: 'Uno', ano: 2020 }, converta-o para uma string JSON formatada com indentação de 4 espaços e exiba no console.

    ✓ Resposta:
    const carro = { marca: 'Fiat', modelo: 'Uno', ano: 2020 };
    const jsonCarro = JSON.stringify(carro, null, 4);
    console.log(jsonCarro);
    // Saída:
    // {
    //     "marca": "Fiat",
    //     "modelo": "Uno",
    //     "ano": 2020
    // }
  3. Qual será a saída do código abaixo? Explique por que.

    const obj = { a: 1, b: undefined, c: function() {} };
    console.log(JSON.stringify(obj));

    ✓ Resposta:

    Saída: {"a":1}. O undefined e a função são ignorados por JSON.stringify, pois não são tipos suportados pelo JSON. Apenas a propriedade a com valor numérico é incluída.

  4. Usando a API JSONPlaceholder (https://jsonplaceholder.typicode.com/posts/1), faça uma requisição fetch para obter o post com id 1 e exiba o título e o corpo no console. Trate possíveis erros.

    ✓ Resposta:
    fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then(response => {
        if (!response.ok) throw new Error('Erro: ' + response.status);
        return response.json();
      })
      .then(post => {
        console.log('Título:', post.title);
        console.log('Corpo:', post.body);
      })
      .catch(error => console.error('Falha na requisição:', error));
  5. Crie uma função que receba um objeto JavaScript e retorne uma string JSON, mas apenas com as propriedades cujo valor seja do tipo string. Use o parâmetro replacer de JSON.stringify.

    ✓ Resposta:
    function filtrarStrings(obj) {
      return JSON.stringify(obj, (key, value) => {
        if (typeof value === 'string') return value;
        return undefined;
      });
    }
    
    const teste = { nome: 'Ana', idade: 30, cidade: 'SP' };
    console.log(filtrarStrings(teste)); // '{"nome":"Ana","cidade":"SP"}'