Conversor de JSON para YAML

Converte JSON em YAML e explica cada aspa que acrescenta: as cadeias que sem ela se tornariam booleanos, números ou datas em silêncio.

Entrada
YAML
name: deploy
country: 'NO'
startsAt: '12:30'
mode: '0755'
version: '1.10'
released: '2024-01-30'
enabled: 'yes'
script: |-
  set -e
  npm run build
  npm test
replicas: 3
tags:
  - web
  - edge
Porque leva aspas cada cadeia

Valores entre aspas: 6. Necessárias só em YAML 1.1: 4. Mude o esquema acima para ver a diferença.

  • countrysó 1.1

    «NO» seria lido como o booleano false.

  • startsAtsó 1.1

    «12:30» seria lido na base 60 como 750.

  • mode

    «0755» tem um zero inicial, por isso seria lido como o número 493.

  • version

    «1.10» seria lido como o número 1.1.

  • releasedsó 1.1

    «2024-01-30» seria lido como uma data e não como texto.

  • enabledsó 1.1

    «yes» seria lido como o booleano true.

O que esta ferramenta faz

Converte um documento JSON em YAML e depois diz a você por que cada cadeia acabou entre aspas. Essa segunda parte é a razão de a ferramenta existir: qualquer outro conversor devolve a saída e deixa-o descobrir mais tarde que um dos seus valores já não é uma cadeia.

A conversão vai num só sentido. Ler YAML é um problema muito maior do que escrevê-lo, e um analisador de YAML parcialmente correto é pior do que nenhum: aceita o seu arquivo e devolve dados errados sem se queixar. Emitir é um problema delimitado, e é esse o sentido coberto aqui.

Por que um conversor precisa de opiniões

O JSON diz de que tipo é cada coisa. Uma cadeia tem aspas, um número não, e não há uma terceira possibilidade. O YAML, em vez disso, decide o tipo de um valor sem aspas olhando para a sua forma: se corresponde ao padrão de um booleano é um booleano, se corresponde a um número é um número, e só se não corresponder a nada é que fica como texto.

É isso que torna o YAML agradável de escrever à mão e que faz da conversão um juízo. Cada cadeia da entrada tem de ser confrontada com cada padrão que o YAML resolve, e posta entre aspas se corresponder a algum. Erre para o lado seguro e a saída fica ruidosa; erre para o outro e um valor muda de tipo em silêncio.

O problema da Noruega e os seus parentes

O caso mais conhecido é uma lista de códigos de país. A Noruega é NO, e em YAML 1.1 o símbolo sem aspas NO é o booleano false. Um arquivo de configuração que enumera países perde a Noruega e ganha um false, e nada em lugar nenhum comunica um erro.

Não é uma regra estranha isolada mas uma família inteira. O YAML 1.1 lê y, Y, yes, no, on e off como booleanos, em qualquer capitalização, o que pega um símbolo químico, a posição de um interruptor e a resposta a uma pergunta. E os resolvedores numéricos são ainda mais estranhos:

  • 12:30 é 750. O YAML 1.1 lê dígitos separados por dois pontos na base 60, por isso uma hora do dia ou uma duração torna-se um inteiro.
  • 0755 é 493. Um zero inicial significa octal em YAML 1.1 — e em YAML 1.2 o mesmo texto é o decimal 755: as duas versões discordam sobre qual número, não sobre se é número.
  • 1.10 é 1.1. Um número de versão em duas partes é um decimal, e o zero final desaparece. Uma dependência fixada em 1.10 passa a apontar para 1.1.
  • 2024-01-30 é um objeto data, não uma cadeia, porque o YAML 1.1 tem um tipo de marca temporal.
  • Uma cadeia vazia é null, tal como as palavras soltas null, Null, NULL e o til.

Nenhum destes é um erro do YAML. São os resolvedores fazendo exatamente o que prometem, sobre texto que por acaso corresponde. A única defesa é pôr entre aspas tudo o que corresponda, que é o que esta ferramenta faz e o que o painel de constatações justifica linha a linha.

Duas versões, e por que a antiga é a padrão

O YAML 1.2 chegou em 2009 e removeu a maioria dos resolvedores surpreendentes. O seu esquema base guarda apenas true e false como booleanos, abandona a base 60 por completo e não tem tipo de marca temporal. Em 1.2, NO e 12:30 e 2024-01-30 são simplesmente cadeias.

O senão está no que realmente lê o seu arquivo. O PyYAML implementa YAML 1.1, e o PyYAML está por trás de uma quantidade enorme de ferramentas — Ansible, clientes antigos de Kubernetes, incontáveis scripts. O yaml.v3 do Go e o js-yaml atual seguem 1.2. Assim, o mesmo documento pode ser lido de duas maneiras conforme quem o abre, e a única saída segura em todo lugar é a que está entre aspas para 1.1.

É esse o padrão aqui. Mudar a configuração para 1.2 não esconde nada: reemite com os resolvedores mais recentes e o painel de constatações encolhe, para que veja com precisão que aspas estavam lá por causa do esquema antigo. As que ficam são as de que qualquer analisador precisa.

Cadeias que partem a sintaxe, não o tipo

Um segundo grupo de cadeias tem de levar aspas por outra razão: não porque o YAML as leria como outro tipo, mas porque não seriam analisadas como texto de todo.

  • Dois pontos seguidos de um espaço terminam uma chave. «note: time: now» seria lido como uma chave note cujo valor é uma chave time.
  • Um espaço seguido de um cardinal inicia um comentário, por isso tudo o que vier a seguir desaparece.
  • Um -, ?, :, [, ], {, }, #, &, *, !, |, >, %, @ ou acento grave inicial é um caractere indicador e significa algo estrutural.
  • Um espaço inicial ou final não é conservado por um valor sem aspas, por isso « x » volta como «x».
  • Uma tabulação em qualquer parte do valor é rejeitada de imediato: o PyYAML recusa o documento inteiro em vez de o ler mal, por isso este caso falha em voz alta.

Usam-se plicas sempre que bastem, porque têm exatamente uma regra de escape — um apóstrofo escreve-se duas vezes — e leem-se melhor do que os escapes com barra invertida que as aspas duplas trazem. As duplas ficam reservadas ao que precisa mesmo de escape: caracteres de controle, tabulações e cadeias de várias linhas que não podem usar um bloco.

Cadeias de várias linhas e o indicador de corte

Uma cadeia com mudanças de linha — um script, um certificado, um bloco de prosa — costuma ser a razão pela qual alguém quer YAML em primeiro lugar. O JSON só a consegue escrever com escapes barra-n numa única linha longuíssima; o YAML tem o escalar de bloco literal, aberto por uma barra vertical, onde o texto aparece indentado e legível por baixo.

A sutileza está no que acontece às mudanças de linha do fim, e isso é governado pelo indicador de corte:

  • Uma barra vertical sozinha apara: por muitas mudanças finais que o bloco tenha, o valor fica exatamente com uma.
  • Uma barra vertical seguida de um menos remove: o valor não fica com nenhuma.
  • Uma barra vertical seguida de um mais conserva: o valor fica com todas.

Esta ferramenta escolhe o indicador a partir da cadeia que recebeu, para que o valor sobreviva à ida e volta exatamente. Vale a pena saber porque o padrão — a barra sozinha — é o que se escreve à mão, e normaliza em silêncio uma cadeia que acabava em duas mudanças de linha ou em nenhuma.

Um escalar de bloco não consegue levar tudo, e onde não consegue, a saída recorre a aspas duplas e o painel de constatações diz por quê. Um retorno de carro não sobrevive, porque os escalares de bloco normalizam as quebras de linha. Uma primeira linha começada por espaço seria lida como indentação adicional e retirada. E uma linha que acaba em espaço é conservada pela especificação, mas é invisível na tela e a maioria dos editores a removem ao salvar, por isso pôr aspas é mais seguro — isso é uma decisão e não uma limitação, e é comunicado como tal.

Mais do que um documento

O YAML tem algo que o JSON não tem: um arquivo pode conter um fluxo de documentos separados por três hífenes. É o formato de um manifesto de Kubernetes, e é a razão pela qual um array JSON tantas vezes precisa de se tornar algo que não é uma sequência YAML.

O interruptor aqui emite cada elemento de um array de topo como documento próprio. E quando a entrada não é um único valor JSON mas cada linha se analisa por si, é lida como NDJSON — o formato delimitado por linhas em que chegam registros e exportações de API — e cada linha passa a documento. Essa leitura é um palpite, por isso é comunicada acima da saída em vez de ser feita em silêncio.

O recurso só se aplica quando a entrada inteira falha a análise e cada linha a consegue, coisa que um documento apenas malformado não fará. Um erro de sintaxe continua surgindo como erro de sintaxe, com a linha e a coluna em que ocorreu.

O que a conversão não consegue preservar

Duas coisas perdem-se antes de esta ferramenta sequer ver os seus dados, ambas na própria análise do JSON, e vale a pena saber quais.

Chaves duplicadas. O JSON permite que um objeto liste a mesma chave duas vezes e a maioria dos analisadores fica com a última em silêncio. O YAML proíbe duplicados por completo, por isso a saída será válida, mas o valor anterior já se perdeu — e nenhum conversor pode comunicar o que nunca recebeu.

Precisão dos inteiros. Um número JSON maior do que cerca de nove quatrilhões não sobrevive à análise para um double, por isso um identificador como 12345678901234567890 volta arredondado. Não é um problema do YAML nem introduzido por esta ferramenta; acontece em todos os analisadores JSON da linguagem. Se um identificador grande importa, o seu lugar é numa cadeia dos dois lados.

Notas sobre a saída

A indentação é com espaços, sempre, porque o YAML proíbe por completo tabulações para indentar: é uma das poucas coisas em que o formato é rigoroso. Dois espaços é a convenção; quatro são oferecidos porque alguns estilos de casa os querem.

As sequências vão indentadas sob a sua chave. Tanto essa forma como a não indentada são YAML válido e significam o mesmo; a indentada é a que a maioria das pessoas escreve e a que a maioria dos editores dobra corretamente.

Um array ou objeto vazio escreve-se em estilo de fluxo como um par de colchetes ou de chaves, porque o estilo de bloco não tem maneira de exprimir o vazio: não há nada para escrever nas linhas seguintes.

A saída acaba com uma mudança de linha, e isso é funcional e não decorativo. O corte de um escalar de bloco mede-se contra a quebra de linha que se lhe segue, por isso um bloco aparado mesmo no fim de um arquivo sem quebra final perde a quebra que devia conservar.

Manifestos do Kubernetes e os campos que precisam continuar cadeias

O Kubernetes lê um manifesto em qualquer dos dois formatos. A documentação dele mesmo chama o YAML de convenção e cita o JSON como alternativa, e o kubectl converte um manifesto para JSON, ou para outra serialização que a API aceite, ao fazer a requisição — converter, portanto, não é sobre o que o cluster vai aceitar. É sobre o arquivo que fica com você: o YAML é o que quem revisa lê, é onde um diff de pull request fica legível, e é o único dos dois capaz de carregar um comentário. O JSON normalmente chega de kubectl get -o json, de um modelo ou de uma API que devolve objetos.

  • Um arquivo, vários objetos. Manifestos podem ser agrupados em um único arquivo separados por três hífenes, e a documentação diz de forma explícita que eles são criados na ordem em que aparecem — por isso um Service costuma ser escrito acima do Deployment que o preenche. O interruptor que emite cada elemento de um array de topo como documento próprio é o que transforma uma lista de objetos JSON em um arquivo assim.
  • As entradas de ambiente de um contêiner são dois campos do tipo cadeia. A referência da API declara tanto name quanto value como cadeias, então um valor JSON igual a "true" precisa sair com as aspas dele: sem elas é um booleano, e um booleano não é o tipo que o campo foi declarado para guardar.
  • Uma porta de contêiner é declarada como inteiro, e fica a poucas linhas daquelas cadeias. Os dois pedem tratamento oposto, e o JSON já carrega a distinção: um número vira um número puro, e uma cadeia só recebe aspas onde o YAML deixaria de lê-la como cadeia.
  • Rótulos e anotações são mapeamentos de cadeia para cadeia. É aí que o problema da Noruega aterrissa dentro de um manifesto: um rótulo cujo valor seja NO, on ou off é um booleano para um analisador de YAML 1.1, e um valor de 1.10 é um decimal que perde o zero final.

Três entradas de ambiente, dadas como JSON com todos os valores em cadeia, se convertem nisto:

env:
  - name: DEBUG
    value: 'true'
  - name: REPLICAS
    value: '3'
  - name: COUNTRY
    value: 'NO'

As três saem com aspas, e o painel de constatações nomeia as três com o valor em que cada uma teria se transformado: true é um booleano, 3 é um número e NO é o booleano false. Só a última depende da versão — mude o esquema para 1.2 e as aspas dela caem, porque o 1.2 não lê NO como nada além de texto. As outras duas mantêm as suas nas duas versões.

Docker Compose, onde a referência do próprio formato alerta sobre o YAML

Um arquivo do Compose é YAML, e a referência que o Docker dedica a ele traz dois alertas sobre a análise de YAML. É incomum a documentação de um produto apontar como perigo o próprio formato de serialização, e os dois alertas tratam exatamente daquilo pelo que esta ferramenta existe: um valor pensado como texto que um analisador lê como outra coisa.

  • As portas. A referência diz que um mapeamento HOST:CONTAINER deve sempre ser dado como cadeia entre aspas, para evitar conflito com o decimal em base 60 do YAML. Sem aspas, 22:22 é o inteiro 1342.
  • Os valores de ambiente. A referência pede que true, false, yes e no fiquem entre aspas para que o analisador não os converta. Dois dos quatro são booleanos nas duas versões do YAML e dois — yes e no — apenas no 1.1, e é por isso que o painel de constatações os marca de forma diferente.
  • A regra da base 60 alcança apenas um mapeamento cujo número depois dos dois-pontos é menor que sessenta, porque esse é o alcance de um único dígito em base 60. Daí 22:22 sair com aspas aqui e 8080:80 ficar puro, e a diferença ser a regra e não um julgamento sobre quais portas importam.

Um serviço cujas portas e valores de ambiente são todas cadeias no JSON se converte nisto:

services:
  proxy:
    image: nginx
    ports:
      - '22:22'
      - 8080:80
    environment:
      TLS_ENABLED: 'no'
      DEBUG: 'true'

Essa é a saída que a referência pede, e o esquema padrão é o que a produz. É também a razão mais clara para não mexer no esquema: mude para 1.2 e o mapeamento de portas e o no perdem as aspas, enquanto o true mantém as dele, porque o 1.2 abandonou a base 60 e mantém como booleanos apenas true e false. Um analisador que implementa 1.2 ainda lê o resultado corretamente — mas o conselho do Docker está escrito sem nenhuma condição desse tipo, e segui-lo custa dois pares de aspas.

Perguntas frequentes

O JSON já é YAML válido?
Em YAML 1.2, sim: a especificação diz isso explicitamente e um analisador 1.2 lê um arquivo JSON diretamente. Na prática não serve de muito, porque a razão para converter é a legibilidade — comentários, escalares de bloco, sem chaves — e colar JSON num arquivo YAML não lhe dá nada disso. Em YAML 1.1 não é bem verdade, o que é mais uma razão para distinguir as duas versões.
Por que a minha cadeia levou aspas de que parece não precisar?
Quase de certeza precisa. O YAML tipifica um valor sem aspas confrontando padrões, por isso NO, yes, off, 12:30, 0755, 1.10, 2024-01-30 e a cadeia vazia deixam todos de ser cadeias. O painel de constatações nomeia cada um e mostra o valor em que se teria tornado, para que verifique em vez de confiar. Se o seu alvo é um analisador YAML 1.2, mudar o esquema tira as que só o 1.1 precisa.
Qual é aqui a diferença entre YAML 1.1 e 1.2?
O 1.2 abandonou os resolvedores que causam quase todas as surpresas: yes/no/on/off já não são booleanos, a base 60 desapareceu e não há tipo de marca temporal. O PyYAML implementa 1.1 e continua em todo lugar, por isso a saída conservadora é a padrão; a configuração 1.2 existe para quando sabe o que vai ler o arquivo.
Consegue converter YAML de volta para JSON?
Não, de propósito. Um leitor de YAML precisa de âncoras, aliases, etiquetas, chaves de fusão, cinco estilos de escalar e duas versões de esquema, e se enganar sutilmente em qualquer um significa aceitar um arquivo e devolver dados diferentes dos que continha. Essa falha é silenciosa, o que a torna pior do que não oferecer a funcionalidade.
Como obtenho um arquivo multidocumento ao estilo do Kubernetes?
Ligue o interruptor que emite cada elemento de um array de topo como documento próprio, e o array passa a documentos separados por três hífenes. Se a sua entrada for NDJSON — um objeto JSON por linha, como muitas vezes chegam registros e exportações de API — isso é detectado automaticamente e comunicado acima da saída.
Por que não há comentários na saída?
Porque não havia nenhum na entrada. Os comentários são a principal coisa que o YAML tem e o JSON não, e um conversor não os pode inventar. Vale a pena lembrar também no sentido inverso: se passar um arquivo YAML por JSON e de volta, todos os seus comentários desapareceram.
Alguma coisa do que colo é enviada para um servidor?
Não. A análise e a conversão rodam inteiramente no seu navegador; nada é carregado nem registrado, e funciona sem conexão à rede.
Como converto a saída JSON do kubectl em um manifesto YAML?
Cole e leia o YAML. O Kubernetes aceita os dois formatos — a documentação dele chama o YAML de convenção e o JSON de alternativa — então a razão para converter está no arquivo que fica com você e não no que o cluster vai aceitar. O que se checa são as aspas: um manifesto é cheio de campos que a API declara como cadeias, entre eles os valores de ambiente e os de rótulos e anotações, e são justamente esses cujo tipo o YAML mudaria. Se o JSON é uma lista de vários objetos, o interruptor de vários documentos faz dela um único arquivo.
Por que o Docker Compose quer minhas portas entre aspas?
Porque, para um analisador de YAML 1.1, 22:22 não é um par de números e sim um único número em base 60 — 1342. A referência do Compose do Docker diz por essa mesma razão que um mapeamento HOST:CONTAINER deve ser sempre uma cadeia entre aspas, e a mesma página pede que true, false, yes e no também fiquem entre aspas dentro de um bloco de ambiente. As duas coisas são o que esta ferramenta faz com o esquema padrão, e o painel de constatações diz de qual regra saiu cada par de aspas. Um mapeamento cujo número depois dos dois-pontos é sessenta ou mais, como 8080:80, está fora da regra da base 60 e fica puro.
Qual versão do YAML escolher para Kubernetes ou Docker Compose?
A padrão, 1.1. Cada par de aspas que ela acrescenta é aceito também por um analisador 1.2, então a saída conservadora é segura de qualquer jeito, e os dois alertas da referência do Compose são sobre regras do 1.1: os mapeamentos de portas em base 60, e yes e no como booleanos. A configuração 1.2 existe para mostrar quais aspas existem só por causa do esquema mais antigo; a saída as perde, o que é o oposto do que aqueles alertas pedem.

Ferramentas relacionadas