Fazer parse de JSON em JavaScript é algo que você vai fazer centenas de vezes como dev web. Felizmente, o JavaScript tem suporte nativo para isso — sem precisar de bibliotecas. Mas há mais nuances em JSON.parse() e JSON.stringify() do que a documentação deixa transparecer. Vamos ver isso direito.

JSON.parse() — transformando uma string em objeto

JSON.parse() recebe uma string formatada em JSON e a converte num valor JavaScript. Esse valor geralmente é um objeto ou array, mas também pode ser uma string, número, booleano ou null:

js
const jsonString = '{"name": "Alice", "age": 30, "active": true}';
const user = JSON.parse(jsonString);

console.log(user.name);    // "Alice"
console.log(user.age);     // 30
console.log(user.active);  // true
console.log(typeof user);  // "object"

Perceba que a entrada precisa ser uma string. Um erro comum é tentar fazer parse de um valor que já é um objeto — JSON.parse({}) lança um SyntaxError porque converte o objeto para "[object Object]" primeiro, que não é JSON válido.

Sempre envolva JSON.parse() em um try/catch

Eis o que ninguém te conta: JSON.parse() lança um SyntaxError se a entrada for JSON inválido. Se você está fazendo parse de dados de uma API, input do usuário ou um arquivo, sempre trate esse erro. Uma única resposta malformada pode derrubar sua aplicação se você não fizer isso:

js
function safeParseJSON(str) {
  try {
    return { data: JSON.parse(str), error: null };
  } catch (err) {
    return { data: null, error: err.message };
  }
}

const { data, error } = safeParseJSON('{"name": "Alice"}');
if (error) {
  console.error('Invalid JSON:', error);
} else {
  console.log(data.name); // "Alice"
}

// Handles bad input gracefully
const result = safeParseJSON('not json at all');
console.log(result.error); // "Unexpected token 'o', "not json "... is not valid JSON"

Uso um wrapper assim em quase todo projeto. Torna o tratamento de erros consistente e evita exceções não capturadas feias chegando até sua UI.

JSON.stringify() — transformando um objeto em string

JSON.stringify() faz o inverso: converte um valor JavaScript em uma string JSON. Você vai usar isso ao enviar dados para uma API, salvar no localStorage ou escrever num arquivo:

js
const user = {
  name: "Bob",
  age: 25,
  roles: ["admin", "editor"],
  password: "secret123" // we'll handle this later
};

// Basic usage
const jsonString = JSON.stringify(user);
// '{"name":"Bob","age":25,"roles":["admin","editor"],"password":"secret123"}'

// Pretty-printed (great for logs and file output)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
//   "name": "Bob",
//   "age": 25,
//   "roles": ["admin", "editor"],
//   "password": "secret123"
// }

O terceiro argumento de JSON.stringify() é o nível de indentação. Usando 2 ou 4 você tem uma saída legível. O padrão (sem terceiro arg) te dá JSON compacto e minificado — melhor para transmissão em rede.

O que stringify() descarta silenciosamente

Esse aqui pega muitos devs de surpresa. JSON.stringify() omite silenciosamente certos valores porque JSON não os suporta:

js
const data = {
  name: "Alice",
  greet: function() { return "hi"; },   // Functions → dropped
  undef: undefined,                      // undefined → dropped
  sym: Symbol("key"),                    // Symbols → dropped
  nan: NaN,                              // NaN → null
  inf: Infinity,                         // Infinity → null
  date: new Date("2024-01-15")           // Dates → ISO string
};

console.log(JSON.stringify(data, null, 2));
// {
//   "name": "Alice",
//   "nan": null,
//   "inf": null,
//   "date": "2024-01-15T00:00:00.000Z"
// }
// greet, undef, sym are GONE
Cuidado: Se você faz stringify de um objeto com valores undefined e depois faz parse, essas chaves vão desaparecer completamente. Isso pode causar bugs sutis se o seu código verificar obj.key === undefined depois.

Buscando JSON de uma API — o padrão do mundo real

Na prática, a maior parte do parse de JSON acontece quando você busca dados de uma API. A Fetch API torna isso direto ao ponto — response.json() cuida do parse pra você:

js
async function getUser(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const user = await response.json(); // parses JSON automatically
    return user;
  } catch (err) {
    console.error('Failed to fetch user:', err);
    return null;
  }
}

const user = await getUser(42);
if (user) {
  console.log(`Hello, ${user.name}!`);
}

response.json() é essencialmente JSON.parse(await response.text()). Também lança erro se o corpo da resposta não for JSON válido, então vale capturar isso separadamente em código de produção.

Trabalhando com dados JSON aninhados

JSON profundamente aninhado é comum em APIs reais. Veja como navegar por ele com segurança sem quebrar em chaves ausentes:

js
const apiResponse = {
  "user": {
    "profile": {
      "address": {
        "city": "Berlin"
      }
    }
  }
};

// Unsafe — throws if any level is undefined
const city1 = apiResponse.user.profile.address.city; // "Berlin"

// Safe with optional chaining (ES2020+) — returns undefined instead of throwing
const city2 = apiResponse?.user?.profile?.address?.city; // "Berlin"
const zip   = apiResponse?.user?.profile?.address?.zip;  // undefined (not a crash)

// With a fallback using nullish coalescing
const country = apiResponse?.user?.profile?.address?.country ?? "Unknown";

Optional chaining (?.) e nullish coalescing (??) são recursos modernos do JavaScript — ambos parte da spec ECMAScript desde 2020 — que tornam o trabalho com estruturas JSON incertas muito mais seguro. Use à vontade — são suportados em todos os navegadores modernos e no Node.js 14+.

As funções replacer e reviver

Tanto JSON.stringify() quanto JSON.parse() aceitam um segundo argumento que permite customizar a transformação. São genuinamente úteis em cenários do mundo real:

js
// replacer: filter or transform values during stringify
const user = { name: "Alice", password: "s3cr3t", age: 30 };
const safe = JSON.stringify(user, ["name", "age"]); // only include these keys
// '{"name":"Alice","age":30}'  — password is excluded

// reviver: transform values during parse
const dateJson = '{"name": "Alice", "createdAt": "2024-01-15T09:30:00Z"}';
const parsed = JSON.parse(dateJson, (key, value) => {
  if (key === "createdAt") return new Date(value); // convert string to Date
  return value;
});
console.log(parsed.createdAt instanceof Date); // true
console.log(parsed.createdAt.getFullYear());   // 2024

O padrão do reviver é especialmente útil para restaurar objetos Date após uma viagem de ida e volta pelo JSON, já que JSON não tem um tipo de data nativo.

Ferramentas úteis

Ao trabalhar com JSON em projetos JavaScript, essas ferramentas economizam tempo: JSON Formatter para formatar respostas minificadas de API, JSON Validator para verificar erros de sintaxe, JSON Path para consultar campos específicos de payloads grandes, e JSON Escape quando precisar embutir JSON em uma string. Para mais profundidade, a referência JSON do MDN é excelente.

Fechando

JSON.parse() e JSON.stringify() são as duas funções que você precisa. Os hábitos chave a construir: sempre envolva parse() em try/catch, use optional chaining para acesso a dados aninhados, e saiba o que stringify() descarta silenciosamente. Acerte esses três e você vai lidar com 99% dos cenários JSON do mundo real sem surpresas.