JSON Toolkit

JSON para YAML na linha de comando: as quatro armadilhas por trás de um comando yq

Resumo: O comando mais copiado dos tutoriais, yq -P input.json > output.yaml, ainda produz JSON no yq v4. Este artigo mede yq v4.44.6, PyYAML 6.0.3 e js-yaml em uma amostra de 2.39 MB, explica por que a extensão da entrada decide o formato de saída e mostra as escritas corretas para conversão em lote, aspas, indentação e precisão de inteiros grandes.

Intermediário~9 min de leituraPublicado em 2026-11-03Atualizado em 2026-11-03
Capa que compara um comando de conversão de JSON para YAML e sua saída em um terminal

Resumo da ópera: yq -P input.json > output.yaml, o comando mais copiado dos tutoriais, ainda produz JSON no yq v4. Para obter YAML é preciso escrever explicitamente -o=yaml (ou -oy); -P é apenas a abreviação de --prettyPrint, ele cuida da indentação e do estilo de bloco, não do formato de saída.

Converter JSON em YAML nunca foi tão simples quanto "trocar o sufixo do arquivo". O custo real se esconde em quatro lugares: o formato de saída é decidido pela extensão da entrada, as regras de aspas reescrevem suas strings em silêncio, a indentação de sequências afeta as ferramentas a jusante e um inteiro grande que passa pelo yq perde precisão de forma permanente.

1. Vamos primeiro executar o comando mais popular

Em uma máquina limpa, com yq v4.44.6 instalado, crie uma entrada mínima:

printf '{"a":1,"b":{"c":2}}' > s.json
yq -P '.' s.json

O que volta não é YAML, e sim JSON indentado:

{
  "a": 1,
  "b": {
    "c": 2
  }
}

Se você seguir o tutorial e redirecionar isso para output.yaml, vai obter um arquivo com sufixo .yaml e conteúdo JSON. Ele é lido normalmente por todos os analisadores YAML 1.2 (no YAML 1.2, JSON é um subconjunto; essa relação de contenção é demonstrada em detalhe em as fronteiras da conversão JSON ↔ YAML e XML), então nada quebra no curto prazo — até o dia em que alguém adiciona um comentário ou uma âncora, ou até o yamllint da CI cuspir uma porção de truthy value should be one of.

A escrita que realmente produz YAML é a que nomeia o formato de saída:

yq -o=yaml '.' s.json
# a: 1
# b:
#   c: 2

2. Por quê: a extensão decide o formato de saída

O comportamento do yq v4 é "o formato de entrada decide o formato de saída". Ele infere o formato a partir da extensão do arquivo de entrada e, sem -o, reutiliza essa inferência. Entrada .json → saída JSON; entrada .yaml → saída YAML; lendo de stdin sem extensão, o padrão é YAML.

A mesma entrada, quatro escritas, medidas:

Comando Origem da entrada Formato realmente produzido Veredito
yq -P '.' s.json arquivo s.json JSON (indentado) armadilha ao cair em .yaml
yq -o=yaml '.' s.json arquivo s.json YAML (estilo de bloco) correto
yq -P -o=yaml '.' s.json arquivo s.json YAML (estilo de bloco) correto, -P supérfluo aqui
cat s.json | yq -P '.' stdin, sem extensão YAML (estilo de bloco) correto, porque o padrão é yaml

A terceira linha diz uma coisa: -P nem sempre é necessário quando -o=yaml já está presente, mas ele também não é inofensivo — veja os números da seção 6. A quarta explica por que "eu copiei e funcionou": muita gente alimenta o yq por um pipe, o que justamente desvia da ramificação da extensão.

3. Conversão em lote: três escritas de shell

Converter um único arquivo é só o começo; os cenários que realmente pedem a linha de comando são quase todos em lote: transformar de uma vez todas as configurações de um diretório, todos os instantâneos de API capturados.

Laço bash / zsh (o mais legível, para um único diretório):

for f in configs/*.json; do
  yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done

find recursivo (quando os subdiretórios também contam):

find . -name '*.json' -exec sh -c 'yq -o=yaml "." "$1" > "${1%.json}.yaml"' _ {} \;

PowerShell (em CI Windows):

Get-ChildItem *.json | ForEach-Object { yq -o=yaml '.' $_.FullName > ($_.BaseName + '.yaml') }

As três escritas têm um pré-requisito rígido em comum: confirme primeiro que o JSON de origem é válido. Um conversor não é um analisador: diante de um erro de sintaxe, muitos conversores entregam apenas uma posição vaga em vez de detalhar os números de linha como faz um validador. Colocar uma verificação prévia dentro do laço custa quase nada:

for f in configs/*.json; do
  jq empty "$f" || { echo "invalid: $f"; continue; }
  yq -o=yaml '.' "$f" > "${f%.json}.yaml"
done

jq empty só analisa, não imprime nada, e em JSON inválido devolve um código de saída não nulo; combinado com set -euo pipefail, ele faz toda a pipeline parar no primeiro arquivo defeituoso em vez de produzir um monte de resultados meio convertidos.

4. Quatro parâmetros de saída, cada um com sua função

A lista de parâmetros do yq é longa, mas no caminho de JSON para YAML só há quatro para guardar:

Parâmetro Função Quando é obrigatório
-o=yaml / -oy define o formato de saída como YAML sempre que a entrada for um arquivo .json
-P / --prettyPrint força o estilo de bloco e normaliza a indentação lendo de stdin, ou quando a indentação de sequência importa
-I=n número de espaços de indentação, padrão 2 quando a jusante exige 4 espaços (alguns guias de estilo do Ansible)
--split-exp divide elementos de array em vários documentos ao gerar manifestos Kubernetes de múltiplas recursos

O caso dos múltiplos documentos merece um parágrafo à parte. Os manifestos do Kubernetes separam vários recursos com --- por convenção. Para gerá-los a partir de um array JSON:

yq -P -o=yaml '.[] | splitDoc' --split-exp 'true' kubernetes-list.json

Sem --split-exp você obtém uma única sequência YAML; com ele, cada elemento do array vira um documento independente separado por ---. O kubectl apply aceita os dois, mas a legibilidade do diff é de outra ordem de grandeza.

5. Aspas: seis formas de string que precisam estar entre aspas

A inferência de tipo do YAML é implícita: uma string que parece um número ou um booleano é analisada como tal se não estiver entre aspas. Isso não é risco teórico — medido no yq v4.44.6, os seis valores a seguir recebem aspas automaticamente, o que prova que o analisador realmente os leria errado:

printf '{"version":"1.0","enabled":"true","port":"8080","empty":"","colon":"host: value","hash":"v # c","unicode":"端口 8080","ts":"2026-11-03"}' | yq -P -o=yaml '.'
version: "1.0"
enabled: "true"
port: "8080"
empty: ""
colon: 'host: value'
hash: 'v # c'
unicode: 端口 8080
ts: "2026-11-03"

As consequências, uma a uma:

Valor original (string JSON) Lido como, sem aspas Impacto real
"1.0" float 1.0 a versão vira decimal; 1.10 vira 1.1
"true" booleano true a lógica condicional se inverte ou os tipos não batem
"8080" inteiro 8080 a porta muda de tipo, a comparação de strings falha
"" null a string vazia desaparece, a jusante recebe None
"host: value" erro de análise ou mapeamento aninhado depois de dois-pontos é preciso haver um espaço
"2026-11-03" objeto de data desserializa como datetime.date, não como string

Essa última é a mais traiçoeira. Em JSON, uma data é apenas uma string; depois da conversão ela vira um tipo data do YAML, e na volta muitas implementações emitem um objeto de data em formato ISO em vez da string original: a consistência de ida e volta se rompe. Tanto o PyYAML quanto o yq optam por acrescentar aspas nesses valores, o que equivale a admitir que o risco é real.

Vale notar de passagem que a linha unicode: 端口 8080 não recebe aspas — o chinês é seguro em YAML. O que deve preocupar você não é isso, e sim a ordem das chaves e a precisão, assunto da próxima seção.

6. Equivalentes em Python e Node, com tempos medidos

Não dá para instalar yq em todo lugar: ambientes offline, imagens de CI travadas, Windows sem gerenciador de pacotes. Os equivalentes em Python e Node existem, cada um com seus próprios padrões prontos para morder. Para escolher entre os três caminhos conforme o cenário, escolha de ferramentas JSON de linha de comando e editores traz uma tabela comparativa completa; este artigo se limita à direção JSON → YAML.

Python + PyYAML (três argumentos, nenhum é opcional):

import json, yaml

with open("config.json", encoding="utf-8") as f:
    data = json.load(f)

with open("config.yaml", "w", encoding="utf-8") as f:
    yaml.safe_dump(
        data, f,
        default_flow_style=False,  # estilo de bloco, não {a: 1, b: 2}
        allow_unicode=True,        # o chinês não é escapado para \u7AEF\u53E3
        sort_keys=False,           # preserva a ordem de chaves do JSON
    )

Esses três têm como padrão False / False / True, ou seja, o PyYAML recém-tirado da caixa reordena as chaves em ordem alfabética e transforma o chinês em \uXXXX. Na mesma entrada, com os padrões, a saída medida é:

apiVersion: v1
data:
  LOG_LEVEL: info
  colon: 'host: value'
  unicode: "\u7AEF\u53E3 8080"
kind: ConfigMap
metadata:
  labels:
    app: web
  name: app-config

Repare que kind acabou depois de apiVersion e antes de metadata. Em manifestos do Kubernetes, é convenção da comunidade que apiVersion venha antes de kind; a ordem das chaves bagunçada não quebra a análise, mas deixa quem faz a revisão muito irritado.

Node + js-yaml:

const fs = require("fs");
const yaml = require("js-yaml");

const data = JSON.parse(fs.readFileSync("config.json", "utf8"));
fs.writeFileSync(
  "config.yaml",
  yaml.dump(data, { lineWidth: -1, noRefs: true, sortKeys: false })
);

lineWidth: -1 desliga a quebra de linhas longas (senão uma URL longa é cortada no meio) e noRefs: true proíbe a geração de âncoras e aliases — obrigatório quando o consumidor é kubectl ou outra ferramenta que não as resolve.

Tempos medidos (amostra: JSON de 2.39 MB, 12000 registros, cada um com um objeto aninhado e dois arrays; máquina: o ambiente sandbox usado para esta tarefa):

Implementação Conversão Análise inversa Ida e volta
yq -o=yaml v4.44.6 0.45 s 0.45 s idêntico
yq -P -o=yaml v4.44.6 4.16 s — idêntico
Node js-yaml 4.x 0.23 s 0.11 s idêntico
Python PyYAML 6.0.3 3.26 s 8.01 s idêntico

Dois pontos contraintuitivos. Primeiro, adicionar -P deixa o yq cerca de 9 vezes mais lento (0.45 s → 4.16 s), enquanto a saída é idêntica byte a byte nesta amostra (2,326,895 bytes nos dois casos) — -P faz uma passada completa de reformatação, um custo nada desprezível que vale economizar quando se convertem centenas de arquivos. Segundo, a análise inversa do PyYAML (8.01 s) é mais de duas vezes mais lenta que a conversão direta (3.26 s): se a sua pipeline precisa reler o YAML repetidamente, Python não é uma boa escolha.

Mais uma: 2.39 MB de JSON viram 2.33 MB de YAML em estilo de bloco, cerca de 3% menor. A crença popular de que "YAML é sempre maior que JSON" não se sustenta — as aspas, vírgulas e chaves do JSON podem pesar mais, em aninhamentos profundos, do que os espaços de indentação do YAML.

7. Três coisas que não voltam

As seções anteriores tratavam de "como converter certo"; esta trata de "o que se perde no caminho". Medindo com uma entrada construída de propósito:

printf '{"order_id":12345678901234567890,"zip":"0150","ts":"2026-11-03","f":1.0e6}' > t.json
yq -o=yaml '.' t.json

A saída do yq:

order_id: 1.2345678901234567e+19
zip: "0150"
ts: "2026-11-03"
f: 1000000

E a volta para JSON: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.

O número de pedido de 20 dígitos 12345678901234567890 virou 12345678901234567000: os três últimos dígitos foram a zero, sem nenhum aviso. O motivo é que o yq passa pelo caminho de parsing float64 do Go; inteiros acima de 2^53 não têm representação exata, então ele emite em notação científica e, na volta, o valor original já não pode ser recuperado.

A mesma entrada entregue ao PyYAML:

order_id: 12345678901234567890
zip: '0150'
ts: '2026-11-03'
f: 1000000.0

Os inteiros do Python são de precisão arbitrária: os 20 dígitos são preservados por completo e a releitura devolve 12345678901234567890. Se os seus dados contêm números de pedido, IDs snowflake ou documentos de identidade desse tipo, não use yq: use Python ou transforme primeiro o campo em string. O mecanismo completo dessa classe de problema de precisão está detalhado em por que inteiros grandes perdem precisão em JSON.

A segunda coisa perdida é a distinção entre float e int: 1.0e6 vira 1000000 (inteiro) na saída do yq, enquanto o PyYAML mantém 1000000.0. A terceira é a ordem das chaves — PyYAML reordena por padrão, yq e js-yaml preservam por padrão, o que é uma fonte oculta de divergência em equipes que misturam linguagens.

8. Na CI, não apenas converta: valide, converta, rode o lint

Tratar a conversão como o único passo é o defeito de pipeline mais comum. Um código de saída 0 na conversão só diz que "o JSON de entrada foi analisado", não diz que a saída está correta nem que a saída satisfaz o consumidor. A forma correta tem três etapas:

#!/usr/bin/env bash
set -euo pipefail

# 1. Validar a origem: camada sintática
jq empty deployment.json

# 2. Converter: nomear explicitamente o formato de saída
yq -o=yaml '.' deployment.json > deployment.yaml

# 3. Validar o produto: legível por um analisador YAML e limpo no lint
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml

Aquele yq -o=json ... > /dev/null do passo 3 parece supérfluo e é na verdade a linha mais útil do bloco: ele força o produto a ser reanalisado. Erros de indentação, falhas de resolução de âncoras e tipos reescritos aparecem todos ali. Pule essa linha e o problema vai esperar até o kubectl apply ou até a aplicação subir.

Se o produto é uma configuração mantida por humanos (values de Helm, docker-compose, workflows do GitHub Actions), confirme ainda três coisas: a ordem das chaves não foi bagunçada, os números longos não perderam dígitos e os comentários ainda precisam ser acrescentados à mão — JSON não tem comentários, e qualquer anotação só poderá ser escrita depois da conversão; nenhum conversor vai inventá-los para você.

Perguntas frequentes

-P ou -o=yaml: afinal, qual escrever?

-o=yaml decide se a saída é YAML; -P decide como esse YAML se parece. Com uma entrada .json, -o=yaml é obrigatório, e -P só entra quando você quer forçar o estilo de bloco ou a indentação de sequências. Lendo de stdin sem extensão, o yq já emite YAML por padrão, então -P sozinho basta.

Posso entregar o YAML convertido direto para o Kubernetes?

Sintaticamente sim, com três ressalvas: as chaves com convenção de ordem como apiVersion não foram reordenadas (o PyYAML reordena por padrão, é preciso sort_keys=False), nenhuma âncora ou alias foi gerado (js-yaml: noRefs: true) e os números longos mantiveram seus dígitos. Para manifestos de múltiplos recursos, divida ainda tudo em documentos separados por --- com --split-exp.

Por que minha saída sai em uma linha só, {a: 1, b: 2}, em vez de indentada?

Isso é estilo flow. É YAML válido, mas é como não ter convertido. Use -P para forçar o estilo de bloco; com PyYAML, confirme default_flow_style=False (o padrão passou a ser False desde o PyYAML 5.1, versões antigas precisam escrevê-lo explicitamente).

Meu número de pedido de 20 dígitos perdeu os últimos dígitos. Como recuperar?

Não se recupera, só se previne. O yq analisa via float64, então inteiros acima de 2^53 perdem precisão por construção. Ou você migra para Python (inteiros de precisão arbitrária, preservação integral verificada) ou armazena os números longos como strings já no JSON — esta última é a mais segura, porque resolve junto o problema equivalente do JSON.parse no lado do JavaScript.

Conversor online ou linha de comando?

Para configurações sigilosas e instantâneos de API com tokens, use a linha de comando local ou uma ferramenta puramente frontend, não faça upload. Para algo pontual, de volume pequeno, em que você quer confirmação visual, uma ferramenta online é mais rápida; a página /convert/yaml deste site roda inteiramente no navegador e os dados não saem da sua máquina. Para tudo que for em lote, reproduzível ou destinado à CI, é linha de comando, com as etapas de validação escritas no mesmo script.