Desenvolvimento

JSON e YAML: as três armadilhas que corrompem dados na conversão

A conversão de dados parece trivial: a API devolve JSON, o pipeline quer YAML, a planilha exporta CSV. Trivial até um código de país "NO" chegar do outro lado como o booleano false, um id de 19 dígitos mudar de valor sozinho e uma versão "1.10" virar o número 1.1. Nenhum desses erros aparece: não há exceção, não há linha vermelha, o documento converte, roda e só quebra semanas depois, em produção, quando ninguém mais lembra que passou por um conversor. Este guia abre as três armadilhas que corrompem dados em silêncio, o problema da Noruega, a perda de precisão de inteiros e as datas implícitas, e some com o mito de que JSON e YAML são intercambiáveis. Cada número aqui foi conferido na especificação: [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259) para JSON, a [spec YAML 1.2.2](https://yaml.org/spec/1.2.2/) para YAML e a ECMAScript para o limite de inteiro. Os exemplos usam o [conversor de dados](tool:conversor-dados), que faz JSON, YAML, CSV e XML no navegador.

J-Kit16 min de leituraIntermediário
  • JSON
  • YAML
  • Serialização
  • Dados
  • Desenvolvimento

Resumo rápido

  • YAML 1.2 é um superconjunto do JSON, mas o schema antigo (1.1) lê "NO", "yes", "on" e "off" como booleanos, o "problema da Noruega".
  • Números em JavaScript são float64: qualquer id acima de 9.007.199.254.740.991 (2^53 − 1) perde precisão ao passar por JSON.parse. Trate ids grandes como string.
  • JSON não tem tipo data (vira string), enquanto o YAML 1.1 converte datas sozinho. Chaves duplicadas, âncoras e yaml.load inseguro fecham a lista de riscos.
  • Na dúvida, use aspas nos escalares ambíguos, confira quais parsers seguem 1.1 ou 1.2, e valide o tipo dos dois lados da conversão.

O que cada formato realmente garante

Antes de converter, vale saber o contrato de cada formato, não a impressão, mas o que a especificação promete. JSON é definido pela RFC 8259 e pela ECMA-404: uma sintaxe minúscula, sem comentários, sem vírgula sobrando, e cujo texto, quando trocado entre sistemas abertos, deve ser codificado em UTF-8. YAML, na spec 1.2.2, é declaradamente um superconjunto do JSON, com foco em legibilidade humana. TOML, na versão 1.0.0 (estável desde janeiro de 2021), nasceu para configuração óbvia e sem ambiguidade. Os três descrevem os mesmos dados; divergem no que assumem por você.

JSON (RFC 8259)

  • Rígido: aspas duplas obrigatórias, nada de comentário ou vírgula final.
  • Sem tipo data e sem inteiro de precisão infinita definidos na spec.
  • Ideal para troca máquina-a-máquina; ruidoso para editar à mão.

YAML (1.2.2)

  • Superconjunto do JSON, com indentação, comentários (#) e âncoras.
  • Muitos escalares implícitos: onde nasce o "problema da Noruega".
  • Limpo para humanos, sensível a um espaço a mais.

TOML (1.0.0)

  • Tipos explícitos: string, inteiro, data-hora e tabela sem adivinhação.
  • Sem indentação significativa; seções entre colchetes.
  • Feito para arquivo de configuração de projeto (Cargo, pyproject).

O conversor de dados trabalha com JSON, YAML, CSV e XML (TOML fica de fora, mas serve de referência quando você quer tipos sem surpresa). CSV e XML acrescentam sua própria armadilha: CSV não tem noção de tipo, toda célula é texto,, e XML mistura elementos com atributos, sem um mapa único para objeto. A tabela abaixo resume o que cada formato do conversor entrega.

Formatos suportados pelo conversor de dados e o que cada um assume.
FormatoTem tipos?Comentários?EspecificaçãoUso típico
JSONSim (limitado)NãoRFC 8259 / ECMA-404APIs, troca de dados
YAMLSim (implícito)Sim (#)YAML 1.2.2Configuração
CSVNão (tudo texto)NãoRFC 4180 (de facto)Planilhas, tabelas
XMLNão (texto + atributos)Sim (marcador)XML 1.0 (W3C)Integrações, documentos

JSON e YAML: superconjunto, não sinônimos

A spec YAML 1.2.2 diz na introdução que o foco principal da versão 1.2 foi "tornar o YAML um superconjunto estrito do JSON" e que ela "removeu muitas das recomendações problemáticas de tipagem implícita". Traduzindo: todo JSON válido é YAML válido, mas nem todo YAML é JSON, e a diferença de estilo é grande. JSON marca estrutura com chaves, colchetes e aspas, feio para o olho, impossível de indentar errado. YAML usa indentação por espaços (tab é erro de sintaxe) e permite omitir aspas, o que fica limpo em configuração ao custo de escalares ambíguos. O mesmo dado nos dois:

// JSON, estrutura explícita / explicit structure
{ "app": "jkit", "port": 3000, "tags": ["web", "tools"] }

# YAML equivalente / equivalent YAML
app: jkit          # comentário permitido / comment allowed
port: 3000
tags:
  - web
  - tools
O mesmo objeto em JSON e em YAML. O YAML de cima é, ele próprio, um JSON válido rescrito.

A palavra que importa na frase da spec é "estrito": a compatibilidade vale para a sintaxe, não para a interpretação de cada valor solto. E é justamente na tipagem implícita, o que um "NO" ou um "1.10" sem aspas viram, que as versões do YAML divergem. Por isso a data da spec importa tanto quanto o número dela. Veja como os padrões chegaram até aqui:

  1. 2001JSON é especificado

    Douglas Crockford descreve o formato em json.org, extraído da sintaxe de objeto do JavaScript.

  2. 2005YAML 1.1

    O schema com tipagem implícita larga: "yes", "no", "on", "off" e "NO" resolvem para booleano. É a origem do problema da Noruega.

  3. 2006RFC 4627

    A primeira RFC do JSON registra o formato como padrão de internet.

  4. 2009YAML 1.2

    Vira superconjunto estrito do JSON e introduz o core schema, que restringe o booleano a true/false.

  5. 2013ECMA-404 e TOML

    A sintaxe do JSON é padronizada como ECMA-404; no mesmo ano nasce o TOML, focado em configuração.

  6. 2017 e 2021RFC 8259, YAML 1.2.2, TOML 1.0.0

    A RFC 8259 (STD 90) consolida o JSON; em 2021 saem a revisão YAML 1.2.2 e a primeira versão estável do TOML.

As três armadilhas que corrompem em silêncio

A maioria dos bugs de conversão não é erro de sintaxe visível, é uma reinterpretação silenciosa de tipo. O documento converte sem reclamar; o dado é que mudou. Três armadilhas causam a maior parte da dor, e as três dependem de qual parser e qual schema você usou.

Armadilha 1, o problema da Noruega. No YAML 1.1, os escalares "NO", "no", "on", "off", "yes", "y" e "n" sem aspas são booleanos. O código do país Noruega é NO; sem aspas, ele resolve para false. O PyYAML segue o schema 1.1 por padrão, então este é o comportamento real de milhões de arquivos. Passe o YAML abaixo por um parser 1.1 e veja o país sumir:

# Entrada YAML (interpretada com o schema 1.1, ex.: PyYAML)
# YAML input (interpreted with the 1.1 schema, e.g. PyYAML)
country: NO
shipping:
  express: yes
  weekend: off

// Saída JSON, os escalares viram booleanos
// JSON output, the scalars become booleans
{
  "country": false,
  "shipping": { "express": true, "weekend": false }
}
O problema da Noruega: "NO" vira false, "yes" vira true, "off" vira false, tudo sem erro.

O YAML 1.2 corrigiu isso: o core schema resolve como booleano apenas true, True, TRUE, false, False e FALSE. Confirme sempre qual schema o seu parser usa. O conversor de dados desta página, por exemplo, roda sobre a biblioteca js-yaml, que na versão instalada usa o core schema 1.2 por padrão, logo, "country: NO" chega como a string "NO", e não como false. A defesa universal, independente do parser, é uma só: coloque aspas em qualquer código, sigla ou valor que possa colidir com um booleano.

Armadilha 2, precisão de inteiros. A spec do JSON não impõe limite ao tamanho de um número, mas JavaScript representa todo número como um ponto flutuante de 64 bits (IEEE 754 binary64). O maior inteiro que cabe sem perda é o Number.MAX_SAFE_INTEGER, definido na especificação ECMAScript. Acima dele, os inteiros pulam de dois em dois, de quatro em quatro, e a conta silenciosamente arredonda.

Number.MAX_SAFE_INTEGER = 2^53 - 1 = 9007199254740991
2^53
a mantissa de um float64 tem 52 bits explícitos + 1 implícito, o que dá 53 bits de precisão inteira
- 1
o maior inteiro representável sem saltos é 2^53 menos um
O maior inteiro que um número JavaScript representa com exatidão. A partir dele, JSON.parse perde precisão.
2^53 − 1maior inteiro seguro em JavaScript
9.007.199.254.740.991esse limite por extenso (16 dígitos)
64 bitstamanho de um id Snowflake ou int64, não cabe
// Entrada JSON, dois ids acima do limite seguro
// JSON input, two ids above the safe limit
{ "orderId": 9007199254740993, "userId": 1541815603606036480 }

// Depois de JSON.parse e serializar de novo (JavaScript)
// After JSON.parse and re-serializing (JavaScript)
{ "orderId": 9007199254740992, "userId": 1541815603606036500 }
O orderId 9007199254740993 vira ...992 (o ímpar não existe em float64); o id de 19 dígitos escorrega 80 unidades.

Armadilha 3, datas. O JSON não tem tipo data: uma data é sempre uma string, e cabe a você combinar o formato (ISO 8601 é o de fato). O YAML 1.1, ao contrário, tem timestamp implícito, um "2026-05-05" sem aspas é convertido para um objeto de data pelo parser, o que muda o tipo do valor sem você pedir. Ao ir de YAML para JSON, esse objeto vira uma string de data que pode não bater com o formato que o outro lado espera. Se o seu campo é uma versão ("2026.05"), um código ou uma data que precisa de fuso, coloque aspas, e, quando o fuso entra na conta, o guia de fusos horários mostra por que UTC e horário de verão transformam uma string aparentemente inofensiva em bug.

Chaves duplicadas, âncoras e a bomba YAML

Além das três armadilhas de tipo, há um segundo grupo de riscos que vive na estrutura e na segurança do parser. Chaves repetidas, âncoras que se multiplicam e um carregador inseguro podem, respectivamente, perder dados, derrubar o servidor e executar código. Cada um dobra para fora do fluxo principal abaixo, abra o que interessa.

Chaves duplicadas: permitidas na sintaxe, indefinidas no efeito

A RFC 8259 (§4) diz que "os nomes dentro de um objeto DEVERIAM ser únicos" e, se não forem, "o comportamento do software que recebe esse objeto é imprevisível". Algumas bibliotecas ficam com o último par, outras com o primeiro, outras acumulam todos, outras dão erro. Ou seja: um JSON com a mesma chave duas vezes é sintaticamente válido, mas o valor que sobrevive depende do parser.

O YAML é mais rígido: a spec proíbe chaves de mapeamento duplicadas. Ainda assim, nem todo parser reclama, vários simplesmente ficam com a última. A defesa é validar unicidade antes de converter, especialmente quando dois arquivos de configuração são mesclados e uma chave aparece nos dois.

Âncoras e aliases: reúso com & e *

O YAML deixa você nomear um nó com uma âncora (&nome) e reusá-lo depois com um alias (*nome), evitando repetição em arquivos grandes. É prático, mas tem duas consequências: ao converter para JSON, os aliases são expandidos, a estrutura reusada é copiada em cada ponto, e o arquivo pode inchar. E, como qualquer valor apontado por alias, a expansão acontece na hora do parse, não na leitura.

Billion laughs: a bomba YAML

O reúso por alias vira arma quando é aninhado. Você define uma âncora com uma lista de dez itens, define uma segunda âncora com dez cópias da primeira, uma terceira com dez cópias da segunda, e assim por diante. Cada nível multiplica o tamanho por dez; poucas linhas de texto se expandem para bilhões de nós na memória. É o ataque billion laughs (ou YAML bomb), um negação de serviço: o parser tenta materializar a estrutura e o processo é morto por falta de memória.

A mitigação é limitar a expansão: os parsers modernos contam as resoluções de alias e param num teto (o pacote yaml do npm limita a 100 por padrão; o SnakeYAML, a 128). Ao aceitar YAML de fonte não confiável, use um parser com esse limite ou rejeite aliases de vez.

Por que o JSON não tem comentários

A gramática da RFC 8259 não prevê comentário: os únicos espaços em branco insignificantes são espaço, tab, quebra de linha e retorno de carro. Um // ou /* */ invalida o documento inteiro. Foi uma escolha deliberada de Douglas Crockford, comentários eram removidos por alguns geradores para embutir diretivas de parsing, o que quebrava a interoperabilidade. Se você precisa de anotação em um JSON, use um campo de dado (por exemplo "_comment"); se precisa de comentário de verdade, esse é um bom sinal de que o formato certo é YAML ou TOML.

Convertendo na prática, sem corromper

Veja uma conversão real de CSV para JSON. A primeira linha do CSV vira as chaves; cada linha seguinte vira um objeto. Como CSV não guarda tipo, todo valor chega como string, inclusive números e booleanos, e um campo vazio vira "". É o caso mais óbvio de perda de tipo, e o que mais surpreende quem espera que o 42 continue número:

name,age,active
Pedro,42,true
Ana,,false

// vira / becomes:
[
  { "name": "Pedro", "age": "42", "active": "true" },
  { "name": "Ana",   "age": "",   "active": "false" }
]
CSV para JSON: o 42 vira a string "42" e o booleano vira "true". Faça a coerção de tipo depois.

No sentido inverso, JSON para CSV, estruturas aninhadas como uma lista de papéis são serializadas como texto dentro da célula, porque uma tabela plana não representa hierarquia. Junte isso ao que já vimos e o mapa de risco fica assim:

As armadilhas da conversão, quem elas afetam e como evitar.
ArmadilhaQuem sofreComo evitar
Booleano acidental (Noruega)Parsers com schema YAML 1.1 (PyYAML)Aspas no valor; use um parser 1.2
Perda de precisão de inteiroJSON.parse e qualquer runtime JavaScriptTrate ids grandes como string
Data convertida sozinhaTimestamp implícito do YAML 1.1Aspas; combine ISO 8601
Tipo apagadoCSV (tudo vira texto)Coaja o tipo após converter
Chave duplicadaJSON (indefinido) e parsers YAML frouxosValide unicidade antes de mesclar
Execução de código / bombayaml.load inseguro; parser sem limite de aliassafe_load e limite de expansão

A melhor forma de internalizar tudo isso é experimentar. Cole um YAML com um "NO" sem aspas, um id gigante ou uma data solta no conversor abaixo e veja o que sai do outro lado, tudo no navegador, sem enviar nada para um servidor:

Teste as armadilhas ao vivo: JSON, YAML, CSV e XML convertidos no navegador.Abrir a ferramenta em página inteira

Localize, converta e valide o tipo

Muitas vezes você não quer o documento inteiro, só um valor lá no fundo. Um caminho (path) é o endereço desse valor: partindo da raiz $, você desce por chaves e índices, $.user.roles[0] pega o primeiro papel do usuário. O localizador de caminhos JSON devolve o caminho de qualquer nó que você clicar, o que ajuda a inspecionar exatamente o campo que vai converter antes de mexer no arquivo todo. Extrair valores por padrão de texto é a outra metade do trabalho, coberta pelo guia de regex do zero.

  • Confirme qual schema o seu parser YAML usa (1.1 vs 1.2) antes de confiar em qualquer escalar sem aspas.
  • Coloque aspas em códigos, siglas, versões e datas que possam colidir com booleano, número ou timestamp.
  • Trate ids e valores monetários acima de 2^53 − 1 como string, do banco ao JSON e de volta.
  • Valide a unicidade das chaves ao mesclar arquivos de configuração.
  • Use safe_load (ou equivalente) para YAML de fonte não confiável, com limite de expansão de alias.
  • Depois de estruturar os dados, formate o resto do pipeline, deixe a query legível no formatador de SQL.

O padrão que resolve quase tudo é o mesmo em qualquer conversão: localize o valor com um caminho, converta com um parser cujo schema você conhece e valide o tipo do outro lado. A conversão nunca é o problema; a suposição de que o tipo sobreviveu intacto é. Se os seus ids seguem outro esquema além de inteiros, o guia de UUID e ULID mostra alternativas que não passam nem perto do limite do float64.

Perguntas frequentes

Por que "NO" vira false quando converto YAML?
É o "problema da Noruega". No schema do YAML 1.1, os escalares sem aspas "NO", "no", "on", "off" e "yes" são booleanos, então o código de país NO resolve para false. Parsers que seguem o 1.1 (como o PyYAML por padrão) fazem isso; o core schema do YAML 1.2 restringe o booleano a true/false. A solução universal é colocar aspas: "NO".
Por que meu id grande mudou de valor ao converter?
Porque números em JavaScript são ponto flutuante de 64 bits e perdem precisão acima de 9.007.199.254.740.991 (2^53 − 1, o Number.MAX_SAFE_INTEGER). Um id como 9007199254740993 é arredondado para 9007199254740992 ao passar por JSON.parse. A solução é tratar esses valores como string desde a origem.
YAML aceita comentário e JSON não?
Correto. YAML usa # para comentários; a gramática do JSON (RFC 8259) não prevê comentário, então // ou /* */ invalidam o arquivo inteiro. Foi uma decisão deliberada de projeto. Se precisa de anotação num JSON, guarde-a num campo de dado (por exemplo "_comment").
Chave duplicada em JSON é erro?
Não é erro de sintaxe, mas é perigoso. A RFC 8259 diz que os nomes deveriam ser únicos e que, quando não são, o comportamento do software é imprevisível: um parser fica com o último valor, outro com o primeiro, outro dá erro. O YAML proíbe chaves duplicadas na spec, embora nem todo parser reclame. Valide a unicidade antes de converter.
yaml.load do PyYAML é seguro?
Não sobre dados não confiáveis. Antes da 5.1, yaml.load() podia executar código arbitrário (CVE-2017-18342) via tags que instanciam objetos Python. Use yaml.safe_load(), que só constrói tipos simples. Da 5.1 em diante o load sem Loader avisa; o FullLoader ainda teve brechas, corrigidas na 5.4. Para entrada externa, safe_load, sempre.
Qual formato usar para configuração: JSON, YAML ou TOML?
YAML costuma vencer quando humanos editam o arquivo, por causa dos comentários e da leitura limpa, mas exige cuidado com escalares implícitos. TOML brilha em configuração de projeto por ter tipos explícitos (inclusive data-hora) e nenhuma ambiguidade. JSON é melhor quando a configuração é gerada e consumida por máquinas, por ser rígido. Escolha pelo editor: humano tende a YAML/TOML, máquina tende a JSON.

JSON e YAML descrevem os mesmos dados com filosofias diferentes, e a conversão é segura só quando você respeita o que cada um assume. As três armadilhas silenciosas são o problema da Noruega (um "NO" que vira false no schema YAML 1.1), a perda de precisão de inteiros acima de 2^53 − 1 = 9.007.199.254.740.991 e as datas que o YAML 1.1 converte sozinho. Some a isso chaves duplicadas, âncoras que explodem em billion laughs e o yaml.load inseguro. A defesa cabe numa frase: use aspas nos escalares ambíguos, trate ids grandes como string, confira o schema do parser e valide o tipo dos dois lados.

Fontes e referências

  1. RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format
  2. ECMA-404, The JSON Data Interchange Syntax
  3. YAML 1.2.2, Especificação oficial (inclui o core schema, §10.3)
  4. ECMAScript (ECMA-262), Number.MAX_SAFE_INTEGER
  5. PyYAML, orientação oficial sobre yaml.load e safe_load
  6. TOML v1.0.0, Especificação
  7. js-yaml, parser YAML usado pelo conversor (schema padrão)