JSON Toolkit

JSON kepada YAML melalui Baris Perintah: Empat Perangkap Di Sebalik Satu Arahan yq

Inti pati: Arahan yang paling kerap disalin dalam setiap tutorial, yq -P input.json > output.yaml, masih mengeluarkan JSON pada yq v4. Artikel ini menguji yq v4.44.6, PyYAML 6.0.3 dan js-yaml pada sampel 2.39 MB, menerangkan mengapa sambungan input menentukan format output, dan memberi resipi yang betul untuk penukaran pukal, petikan, inden dan ketepatan integer besar.

Pertengahan~9 min bacaanDiterbitkan 2026-11-03Dikemas kini 2026-11-03
Kulit yang membandingkan arahan penukaran JSON kepada YAML dengan outputnya dalam terminal

Kesimpulan utama: yq -P input.json > output.yaml — arahan yang paling banyak disalin dalam tutorial — masih menghasilkan JSON pada yq v4. Untuk mendapatkan YAML anda mesti menyatakan -o=yaml (atau -oy) secara eksplisit. -P ialah singkatan bagi --prettyPrint: ia mengawal inden dan gaya blok, bukan format output.

Menukar JSON kepada YAML tidak pernah semudah menukar sambungan fail. Kos sebenar tersembunyi di empat tempat: sambungan input menentukan format output, peraturan petikan diam-diam menulis semula rentetan anda, inden urutan mempengaruhi alat hiliran, dan integer besar yang melalui yq hilang ketepatan secara kekal.

1. Jalankan dahulu arahan yang paling popular

Pada mesin bersih dengan yq v4.44.6 dipasang, cipta satu input minimum:

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

Yang kembali bukan YAML, tetapi JSON berinden:

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

Jika anda mengikut tutorial dan mengarah semula output itu ke output.yaml, anda mendapat fail bersambungan .yaml yang kandungannya JSON. Setiap penghurai YAML 1.2 membacanya dengan gembira kerana JSON ialah subset YAML 1.2, dan hujah pengandungan penuhnya ada dalam sempadan penukaran JSON, YAML dan XML, jadi tiada apa yang rosak serta-merta. Ia rosak pada hari seseorang menambah komen atau sauh, atau apabila yamllint dalam CI mula memuntahkan dinding truthy value should be one of.

Kesilapan ini paling senyap dalam pasukan yang sudah ada saluran pelancaran: fail dijana dalam satu job, disimpan berbulan-bulan, dan semua orang menganggapnya YAML semata-mata kerana ia berakhir dengan .yaml, tanpa sesiapa pun membuka kandungannya. Penggera hanya berbunyi apabila yamllint ditambah ke saluran, atau apabila rakan sepasukan cuba menyisipkan ulasan dengan # lalu penghurai mengeluh tentang sintaks yang dalam JSON tidak wujud langsung. Ketika itu berpuluh artifact sudah terhasil dengan format yang salah dan kesemuanya perlu dijana semula.

Arahan yang benar-benar menghasilkan YAML menamakan format output:

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

2. Mengapa: sambungan menentukan format output

yq v4 mengikut peraturan "format input menentukan format output". Ia membuat inferens format daripada sambungan fail input dan, tanpa -o, menggunakan semula inferens itu. Input .json → output JSON. Input .yaml → output YAML. Membaca daripada stdin tanpa sambungan, lalainya ialah YAML.

Empat arahan pada input yang sama, diukur:

Arahan Sumber input Format output sebenar Keputusan
yq -P '.' s.json fail s.json JSON (berinden) perangkap bila diarah ke .yaml
yq -o=yaml '.' s.json fail s.json YAML (gaya blok) betul
yq -P -o=yaml '.' s.json fail s.json YAML (gaya blok) betul, -P berlebihan di sini
cat s.json | yq -P '.' stdin, tiada sambungan YAML (gaya blok) betul, kerana lalai yaml

Baris ketiga memberitahu satu perkara penting: -P tidak sentiasa perlu apabila -o=yaml sudah ada, tetapi ia juga bukan tanpa kos — lihat data masa di bahagian keenam. Baris keempat menjelaskan mengapa "saya salin dan ia terus berfungsi": ramai orang menyalurkan data melalui paip, yang kebetulan melangkaui cabang sambungan sama sekali.

Perlu ditekankan juga bezanya dengan jq, yang sentiasa memulangkan JSON tidak kira sambungan fail. Sesiapa yang datang dari jq terbiasa dengan anggapan bahawa alat itu "sudah tahu" format yang dikehendaki, dan itulah sebabnya -P tidak pernah disyaki. Sebaliknya, sesiapa yang memberi data dengan cat fail | yq tidak akan nampak masalah itu langsung, kerana pada laluan itu yq sudah mengandaikan YAML. Percabangan dua laluan inilah sebab perangkap ini kekal dalam begitu banyak panduan: setiap pembaca mencuba satu varian, kedua-duanya "berfungsi", tetapi hanya satu yang berfungsi atas sebab yang betul.

3. Penukaran pukal: tiga resipi shell

Menukar satu fail cuma titik mula. Hampir setiap sebab sebenar untuk melakukannya pada baris perintah ialah secara pukal: setiap fail konfigurasi dalam satu direktori, setiap tangkapan API yang disimpan, semuanya sekali gus.

Gelung bash / zsh (paling mudah dibaca, untuk satu direktori):

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

find secara rekursif (apabila subdirektori juga penting):

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

PowerShell (pada CI Windows):

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

Ketiga-tiganya berkongsi satu prasyarat keras: sahkan JSON sumber dahulu. Penukar bukan pengesah. Apabila berdepan ralat sintaks, banyak penukar hanya memberi kedudukan yang kabur, bukan perincian per baris yang diberikan pengesah. Menambah satu pemeriksaan di dalam gelung hampir tidak ada kosnya:

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

jq empty menghurai tanpa mencetak dan keluar dengan kod bukan sifar apabila JSON tidak sah. Digabungkan dengan set -euo pipefail, saluran berhenti pada fail rosak yang pertama dan bukannya menghasilkan longgokan output separuh siap. Pengesah berasaskan skema lazimnya menunjukkan laluan medan dengan instancePath; penukar jarang memberi ketepatan sebegitu, dan itulah sebabnya semakan awal bukan satu pembaziran tetapi jaring keselamatan.

Dua butiran yang paling kerap menyebabkan sakit kepala di persekitaran sebenar. Pertama, petikan di sekeliling "$f": tanpanya, nama fail yang ada spasi — lazim dalam folder kongsi pasukan dokumentasi — memecahkan laluan dan gelung menulis ke tempat yang salah. Kedua, ${f%.json}.yaml memotong akhiran dari sebelah kanan: jika fail anda bernama config.prod.json, hasilnya config.prod.yaml, tepat seperti yang dikehendaki, tetapi lebih baik mengujinya sekali sebelum melepaskan gelung ke atas ratusan fail.

4. Empat flag output, empat tugas berasingan

Senarai flag yq panjang, tetapi hanya empat yang penting pada laluan JSON ke YAML:

Flag Apa yang dilakukannya Bila anda mesti menyatakannya
-o=yaml / -oy Menetapkan format output kepada YAML Setiap kali input ialah fail .json
-P / --prettyPrint Memaksa gaya blok dan menormalkan inden Membaca stdin, atau bila inden urutan penting
-I=n Lebar inden, lalai 2 Hiliran mahu 4 spasi (sesetengah panduan gaya Ansible)
--split-exp Memecah unsur tatasusunan kepada berbilang dokumen Menjana manifes Kubernetes berbilang sumber

Kes berbilang dokumen patut disebut berasingan. Manifes Kubernetes lazimnya mengasingkan sumber dengan ---. Menjananya daripada tatasusunan JSON:

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

Tanpa --split-exp anda mendapat satu urutan YAML; dengannya, setiap unsur tatasusunan menjadi dokumen berasingan yang dipisahkan ---. kubectl apply menerima kedua-duanya, tetapi kebolehbacaan diff berbeza satu order magnitud: dalam semakan kod, tatasusunan gergasi memaksa orang mengira pendakap, manakala dokumen berasingan memaparkan setiap sumber dengan lengkap.

Flag -I=n ialah yang paling kerap terlupa. yamllint dan sebahagian besar playbook Ansible menuntut inden tertentu; jika anda menjana YAML dengan 2 spasi sedangkan repositori guna 4, fail itu sah tetapi lint menolaknya pada commit pertama. Menetapkan -I=4 dalam skrip penukaran mengelakkan diff bising yang mana setiap baris berubah semata-mata kerana indennya.

5. Petikan: enam bentuk rentetan yang mesti dipetik

Inferens jenis YAML adalah tersirat. Rentetan yang kelihatan seperti nombor atau boolean dihurai sedemikian kecuali ia dipetik. Ini bukan risiko teori: diukur pada yq v4.44.6, kesemua enam nilai berikut dipetik secara automatik, yang membuktikan penghurai memang akan tersalah baca:

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"

Akibatnya, satu per satu:

Nilai asal (rentetan JSON) Dibaca sebagai, tanpa petikan Kesan sebenar
"1.0" Float 1.0 Versi menjadi perpuluhan; 1.10 runtuh kepada 1.1
"true" Boolean true Logik cabang songsang atau jenis tidak sepadan
"8080" Integer 8080 Jenis port berubah, perbandingan rentetan gagal
"" null Rentetan kosong lenyap, hiliran menerima None
"host: value" Ralat penghuraian atau pemetaan bersarang Selepas titik bertindih mesti ada spasi
"2026-11-03" Objek tarikh Dinyahsiri kepada datetime.date, bukan rentetan

Yang terakhir paling licik. Dalam JSON tarikh cuma rentetan; selepas penukaran ia menjadi tarikh YAML, dan banyak implementasi menyirkannya semula sebagai objek tarikh dan bukannya rentetan asal, lalu memutuskan konsistensi perjalanan pergi balik. Kedua-dua PyYAML dan yq menambah petikan di sini, iaitu pengakuan bahawa risikonya benar.

Perhatikan juga bahawa unicode: 端口 8080 dikeluarkan tanpa petikan — teks CJK selamat dalam YAML. Yang patut dirisaukan ialah susunan kunci dan ketepatan, dibincangkan seterusnya.

Dalam konfigurasi yang ditulis pasukan berbahasa Melayu ada satu lagi kes yang tidak masuk jadual tetapi wajar diawasi: nilai seperti ya dan tidak, tarikh berformat 03/11/2026, serta kod bersifar pendahulu seperti poskod 0150 atau nombor kad pengenalan. Dengan penghurai YAML 1.1 — yang masih ada dalam sesetengah pustaka lama — rentetan no ditafsir sebagai boolean false, jadi medan seperti aktif: no bertukar makna tanpa sesiapa menyentuh kodnya. Penawarnya tetap sama: petik setiap nilai yang "nampak seperti benda lain", dan jangan percaya penukar dapat meneka niat anda. Kod yang ada sifar di depan termasuk golongan sama: tanpa petikan ia menjadi nombor dan sifarnya hilang.

6. Persamaan Python dan Node, dengan masa terukur

Anda tidak boleh memasang yq di semua tempat: mesin terasing, imej CI terkunci, Windows tanpa pengurus pakej. Persamaan Python dan Node wujud, setiap satu dengan lalainya sendiri yang menunggu untuk menggigit. Untuk perbandingan mengikut senario bagi ketiga-tiga laluan, lihat pemilihan alat CLI dan editor JSON; artikel ini kekal pada arah JSON → YAML.

Python + PyYAML (tiga argumen, tiada satu pun opsional):

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,  # block style, not {a: 1, b: 2}
        allow_unicode=True,        # keep CJK as-is, not \u7AEF\u53E3
        sort_keys=False,           # preserve the original key order
    )

Tiga itu lalainya False / False / True, ertinya PyYAML yang baru dipasang menyusun kunci mengikut abjad dan melarikan CJK ke \uXXXX. Diukur pada input sama dengan lalai:

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

Perhatikan kind kini tersusun selepas apiVersion dan sebelum metadata. Konvensi Kubernetes meletakkan apiVersion sebelum kind; penyusunan semula tidak memecahkan penghuraian, tetapi ia menyengsarakan semakan: sesiapa yang meneliti manifes menjangkakan susunan yang dikenali, dan diff yang menyusun semula semua kunci nampak jauh lebih besar daripada yang sebenarnya.

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 mematikan lipatan baris (jika tidak, URL panjang terputus di tengah) dan noRefs: true menyekat sauh dan alias — wajib apabila pengguna ialah kubectl atau alat lain yang tidak menyelesaikannya.

Masa terukur (sampel: JSON 2.39 MB, 12000 rekod, setiap satu dengan objek bersarang dan dua tatasusunan; mesin ialah sandbox yang digunakan untuk tugasan ini):

Implementasi Menukar Menghurai balik Pergi balik
yq -o=yaml v4.44.6 0.45 s 0.45 s sepadan
yq -P -o=yaml v4.44.6 4.16 s — sepadan
Node js-yaml 4.x 0.23 s 0.11 s sepadan
Python PyYAML 6.0.3 3.26 s 8.01 s sepadan

Dua keputusan yang berlawanan dengan gerak hati. Pertama, menambah -P menjadikan yq kira-kira 9 kali lebih perlahan (0.45 s → 4.16 s) sedangkan pada sampel ini outputnya sama bait demi bait, iaitu 2,326,895 bait kedua-duanya — -P melakukan satu pasangan susun atur semula sepenuhnya, dan kos itu terkumpul apabila menukar ratusan fail dalam satu job. Kedua, penghuraian songsang PyYAML (8.01 s) lebih dua kali ganda daripada dapan hadapannya (3.26 s), jadi Python pilihan buruk jika saluran anda membaca YAML berulang kali.

Satu lagi: 2.39 MB JSON menjadi 2.33 MB YAML gaya blok, kira-kira 3% lebih kecil. Tanggapan popular bahawa "YAML sentiasa lebih besar daripada JSON" tidak benar — pada sarangan yang dalam, petikan, koma dan pendakap kerinting JSON boleh mengatasi ruang kosong inden YAML. Dalam amalan, persekitaran selalunya yang menentukan laluan sebelum jadual masa sempat dipertimbangkan: jika runner sudah ada Node, js-yaml paling pantas dan tidak perlu memuat turun binari; jika pasukan bekerja luar talian dengan Python sedia ada, PyYAML satu-satunya jalan dan kos bacaan semulanya perlu diterima.

7. Tiga perkara yang tidak kembali

Bahagian sebelum ini ialah tentang menukar dengan betul. Yang ini tentang apa yang hilang. Satu input yang sengaja dibina, diukur:

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

yq mengeluarkan:

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

Dan ditukar balik: {"order_id":12345678901234567000,"zip":"0150","ts":"2026-11-03","f":1000000}.

Id pesanan 20 digit 12345678901234567890 menjadi 12345678901234567000 — tiga digit terakhir menjadi sifar, tanpa sebarang amaran. yq menghurai melalui laluan float64 Go; integer melebihi 2^53 tidak boleh diwakili dengan tepat, jadi ia beralih kepada notasi saintifik dan nilai asal sudah hilang semasa anda menukar balik.

Input sama melalui PyYAML:

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

Integer Python berketepatan sewenang-wenangnya, jadi kesemua 20 digit kekal dan perjalanan pergi balik utuh. Jika data anda mengandungi id pesanan, snowflake id atau nombor kad pengenalan, jangan guna yq — guna Python, atau simpan medan itu sebagai rentetan dahulu. Mekanisme penuh di sebalik kegagalan kelas ini diterangkan dalam mengapa integer besar hilang ketepatan dalam JSON.

Kehilangan kedua ialah bezanya float dengan int: 1.0e6 menjadi 1000000 pada output yq manakala PyYAML mengekalkan 1000000.0. Yang ketiga ialah susunan kunci — PyYAML menyusun secara lalai, yq dan js-yaml mengekalkan secara lalai, iaitu punca tersembunyi perselisihan dalam pasukan berbilang bahasa.

Bacaan bahagian ini lebih tidak selesa untuk kedai dalam talian dan gerbang pembayaran: id pesanan atau id transaksi 20 digit tidak ketara dalam diff kerana bezaannya cuma tiga sifar di hujung, dan fail itu kekal YAML yang sah. Satu-satunya pertahanan yang boleh dipercayai ialah ujian pergi balik yang membandingkan JSON asal dengan hasil JSON → YAML → JSON medan demi medan, dan gagal walau satu digit berubah. Membandingkan sekadar bilangan bait atau bilangan medan tidak memadai: di sini kedua-dua fail bersaiz tepat sama.

8. Dalam CI, jangan sekadar convert: sahkan, tukar, lint

Menganggap penukaran sebagai satu-satunya langkah ialah kecacatan saluran yang paling biasa. Kod keluar sifar bermaksud "JSON input berjaya dihurai", bukan "output betul" dan bukan "output memenuhi keperluan pengguna". Bentuk tiga peringkat yang betul:

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

# 1. Validate the source: syntax layer
jq empty deployment.json

# 2. Convert: name the output format explicitly
yq -o=yaml '.' deployment.json > deployment.yaml

# 3. Validate the artifact: parseable YAML, and lint-clean
yq -o=json -I=0 '.' deployment.yaml > /dev/null
yamllint deployment.yaml

Baris yq -o=json ... > /dev/null pada langkah 3 nampak berlebihan tetapi ialah baris paling berguna dalam blok itu: ia memaksa artifact dihurai semula. Kesilapan inden, kegagalan menyelesaikan sauh dan jenis yang ditulis semula terserlah di situ. Langkaunya dan masalah menunggu hingga kubectl apply atau hingga aplikasi mula, iaitu selepas ia sudah dilancarkan.

Jika artifact ialah konfigurasi yang diselenggara manusia, seperti values Helm, docker-compose atau aliran kerja GitHub Actions, sahkan tiga perkara lagi: susunan kunci tidak terharu-biru, nombor panjang tidak hilang digit, dan komen masih perlu ditambah dengan tangan. JSON tiada komen, jadi setiap anotasi mesti ditulis selepas penukaran; tiada penukar akan menciptanya untuk anda.

Dalam workflow GitHub Actions corak ini masuk tanpa geseran: job pengesahan berjalan pada runner bersih, jq dan yamllint dipasang pada langkah yang sama, dan skrip hanya menerbitkan artifact jika ketiga-tiga peringkat lulus. Berbaloi untuk mengunci versi alat dalam workflow, termasuk versi yq, kerana kelakuan yang diterangkan di bahagian kedua ialah kelakuan yq v4 dan tidak semestinya kelakuan versi yang akan dipasang secara lalai oleh runner pada tahun depan.

Soalan Lazim

Patutkah saya guna yq -P atau -o=yaml?

-o=yaml menentukan sama ada output ialah YAML; -P menentukan rupa YAML itu. Dengan input .json, -o=yaml wajib, dan -P hanya perlu apabila anda mahu gaya blok paksa atau inden urutan. Membaca daripada stdin tanpa sambungan, yq sudah lalai kepada YAML, jadi -P sahaja memadai.

Bolehkah YAML yang ditukar terus diberi kepada Kubernetes?

Secara sintaks ya, dengan tiga syarat: kunci yang ada konvensi susunan seperti apiVersion tidak disusun semula (PyYAML menyusun secara lalai; tetapkan sort_keys=False), tiada sauh atau alias dijana (js-yaml: tetapkan noRefs: true), dan nombor panjang mengekalkan digitnya. Untuk manifes berbilang sumber, pecahkan kepada dokumen berpisah --- dengan --split-exp.

Mengapa output saya satu baris {a: 1, b: 2} dan bukannya berinden?

Itu gaya aliran. Ia YAML yang sah tetapi menewaskan tujuan penukaran. Guna -P untuk memaksa gaya blok; dengan PyYAML, sahkan default_flow_style=False kerana ia lalai False sejak PyYAML 5.1 dan versi lama perlu menulisnya secara eksplisit.

Id pesanan 20 digit saya hilang digit terakhir. Bolehkah saya pulihkannya?

Tidak, cegah sahaja. yq menghurai melalui float64, jadi integer melebihi 2^53 hilang ketepatan secara pembinaan. Sama ada beralih ke Python, iaitu integer berketepatan sewenang-wenang yang disahkan tanpa kehilangan, atau simpan nombor panjang sebagai rentetan dalam JSON, yang turut membaiki masalah setara JSON.parse di sebelah JavaScript.

Alat dalam talian atau baris perintah?

Untuk konfigurasi berahsia dan tangkapan API yang ada token, guna baris perintah atau alat sebelah klien, jangan muat naik. Untuk kerja sekali gus, kecil, yang mahu pengesahan visual, alat dalam talian lebih pantas; halaman /convert/yaml laman ini berjalan sepenuhnya dalam pelayar anda dan data tidak meninggalkan mesin. Untuk apa-apa yang pukal, boleh diulang atau terikat pada CI, guna baris perintah dan tulis langkah pengesahan ke dalam skrip yang sama.