JSON Toolkit

JSON yang sama, empat alat, empat hasil: cara memilih formatter

Inti pati: Pemformatan bukan tanpa kehilangan. JSON yang sama dengan sifar pendahulu, BOM atau integer 20 digit menghasilkan keputusan berbeza pada Python, Node, jq dan json.tool: ada yang melaporkan ralat, ada yang diam-diam mengubah nilai. Sepuluh sampel dijalankan sendiri merentas empat laluan menghasilkan jadual pemilihan dan skrip pengesahan ketat.

Intermediate~6 min bacaanDiterbitkan 2026-11-03Dikemas kini 2026-11-03
Empat laluan penghuraian memberi hasil berbeza bagi JSON yang sama

Kesimpulan: perbezaan paling kritikal antara alat format JSON bukan berapa banyak ruang inden, tetapi input yang sama boleh mencetuskan ralat pada satu alat dan diam-diam menukar data pada alat lain. Sebelum memilih, jalankan satu sampel bernilai sempadan melalui alat itu — ia lebih berguna daripada senarai ciri.

Ramai menyangka format sekadar «menyusun semula teks». Hakikatnya setiap laluan mentafsir semula teks dengan parser sendiri. Bila parser longgar, ralat yang sepatutnya dilemparkan bertukar menjadi perubahan nilai senyap — manakala halaman terus memaparkan tanda hijau «Berjaya diformat» yang menenangkan.

1. Fenomena: satu input, empat laluan, empat hasil

Persekitaran ujian: Python 3.11.1, Node v22.13.1, jq 1.7. Sepuluh rentetan JSON pendek binaan tangan dimasukkan melalui stdin, tanpa ditulis ke cakera, tanpa mengubah konfigurasi. Empat laluan: json.loads, JSON.parse, jq -c ., python -m json.tool. ERR bermaksud melempar pengecualian lalu berhenti, SILENT bermaksud tiada ralat tetapi output sudah berbeza daripada input.

Sampel python json.loads node JSON.parse jq -c . python -m json.tool
S1 {"a":1,"b":[1,2]} OK OK OK OK
S2 {"a":1,} ERR ERR ERR ERR
S3 {'a':1} ERR ERR ERR ERR
S4 {"a":01} ERR char 6 ERR position 6 SILENT {"a":1} ERR
S5 {"a":1 /*x*/} ERR char 7 ERR ERR ERR
S6 \ufeff{"a":1} ERR utf-8-sig ERR position 0 SILENT {"a":1} ERR
S7 {"id":9007199254740993} OK exact SILENT 9007199254740992 OK exact OK
S8 {"a":1,"a":2} SILENT {"a":2} SILENT {"a":2} SILENT {"a":2} OK
S9 {"a":1 ERR char 6 ERR ERR EOF ERR
S10 <html>502</html> ERR char 0 ERR Unexpected token '<' ERR ERR

Empat daripada sepuluh sampel menghasilkan «tiada ralat tetapi data berubah»: S4, S6, S7, S8. Maksudnya jika hanya percaya pada lampu hijau «Berjaya diformat», ada kebarangkalian empat puluh peratus anda sudah memegang data yang bukan lagi yang asal. Yang memerangkap: tiada satu pun kes ini tersenarai dalam senarai ralat sintaks — fail boleh dibuka, disalin, dieksport, dan kesilapannya terletak pada kandungan, bukan sintaks.

2. Punca: lapisan sintaks dan lapisan semantik dua perkara berbeza

RFC 8259 menyatakannya terus terang: seksyen 1.2 mengesyorkan mengelak beberapa amalan yang dibenarkan ECMA-404 demi interoperabiliti maksimum; seksyen 4 mewajibkan nama objek unik hanya sebagai SHOULD, tanpa menetapkan bagaimana kunci pendua dikendalikan; seksyen 6 tentang nombor hanya memberi syor interoperabiliti. ECMA-404 melangkah lebih jauh, secara jelas mengisytiharkan dirinya «agnostik terhadap semantik nombor» — hanya mengawal perwakilan jujukan nombor, bukan penyimpanan dalam bahasa. Ringkasnya, spesifikasi menyerahkan kawasan sempadan yang luas kepada pembina.

Maka «format» terpecah kepada dua lapisan: lapisan sintaks memutuskan sama ada teks sah, lapisan semantik memutuskan teks itu menjadi nilai apa. Yang pertama ada jawapan piawai, yang kedua tidak. jq membaca 01 sebagai 1 dan melangkau BOM di awal ialah pilihan pada lapisan semantik; Node memaksa integer 20 digit ke dalam double IEEE 754 ialah pilihan runtime; empat laluan sama-sama menyimpan nilai terakhir bagi {"a":1,"a":2} ialah konvensi bersama parser besar. Tiada yang melanggar «boleh di-parse», tetapi semuanya menukar data.

Cara membetulkan kesilapan hurai JSON berkerja pada lapisan sintaks, menterjemah teks ralat menjadi koordinat. Artikel ini mengurus langkah sebelumnya — jika alat langsung tidak melaporkan ralat, koordinat pun tidak anda peroleh.

3. Empat perubahan data senyap, satu demi satu

Asingkan empat sampel senyap, perubahannya kecil sehingga terlepas pandang ketika dibaca sepintas lalu:

{"a": 01}                          // leading zero: jq emits {"a":1}
\ufeff{"a": 1}                     // BOM: jq skips the first three bytes
{"id": 9007199254740993}           // 20-digit int: JSON.parse gives ...992
{"a": 1, "a": 2}                   // duplicate key: all four keep {"a":2}

Sifar di hadapan dan BOM tergolong dalam jenis «input memang sudah tidak sah, tetapi alat memaafkannya». 01 haram dalam JSON piawai (RFC 8259 seksyen 6), BOM juga tidak patut berada di awal teks JSON; Python dan Node kedua-duanya menolak dengan jelas, jq memilih untuk terus. Kelonggaran ini mudah ketika membersihkan log, tetapi malapetaka ketika semakan kontrak: huluan menyangka ia menghantar data salah yang perlu dibetulkan, sedangkan hiliran sudah memegang hasil «yang nampak normal».

Integer besar tergolong dalam jenis «input sah sepenuhnya, runtime tidak mampu menampungnya». 9007199254740993 ialah nombor JSON yang sah, tetapi melebihi 2^53-1; JSON.parse hanya boleh memaparkan 9007199254740992. int Python dan jq mengekalkan nilai asal. Jika ini nombor pesanan atau Snowflake ID, satu digit terakhir berubah bermakna padanan akaun tidak lagi sepadan.

Kunci pendua tergolong dalam jenis «spesifikasi berdiam, implementasi memilih sendiri». Keempat-empat laluan senyap mengambil nilai terakhir, tanpa sebarang amaran. RFC 8259 hanya kata SHOULD, jadi ini bukan pepijat — tetapi ia kelas masalah paling sukar dikesan ketika menggabung konfigurasi atau mencantum patch.

4. Keupayaan mengesan ralat: siapa beri baris lajur, siapa satu ayat sahaja

Lihat pula enam sampel yang gagal, jumlah maklumat daripada empat laluan berbeza jauh. JSONDecodeError Python memberi msg, lineno, colno, dan pos serentak; Node v22 melaporkan position N (line 1 column M) serta petikan teks di titik ralat; python -m json.tool menggunakan semula maklumat Python; jq hanya memberi satu ayat parse error: ... at line N tanpa ofset aksara.

Pada JSON termampat satu baris, nombor baris dan lajur hampir tidak berguna — semuanya di baris 1. Yang diperlukan ialah ofset aksara. Skrip berikut menyeragamkan semakan menjadi «ralat + ofset + konteks»:

import json, sys

def strict_check(text: str) -> None:
    try:
        json.loads(text)
        print("OK")
    except json.JSONDecodeError as e:
        print(f"ERR {e.msg} | char {e.pos} | line {e.lineno} col {e.colno}")
        lo, hi = max(0, e.pos - 30), min(len(text), e.pos + 30)
        print("...", text[lo:hi].replace("\n", "\\n"), "...")
        sys.exit(1)

strict_check(open(sys.argv[1], encoding="utf-8").read())

Ia lebih ketat daripada jq . (menolak sifar di hadapan dan BOM) dan memberi e.pos. Cara menukar line 1 column 23611 kepada kedudukan bait, dan mengapa ofset aksara dengan ofset bait dalam dokumen CJK terbeza tiga kali ganda, sudah dihuraikan di Penyahbugan JSON Diminify, tidak diulang di sini.

5. Cara membetulkan: pecahkan format kepada «semakan ketat» + «mencantikkan»

Kesimpulannya mudah: jangan biarkan satu butang «Format» memikul semakan dan pencantikan serentak. Gunakan parser ketat untuk menentukan kesahan dahulu, baru pilih alat untuk menyusun rupa. Satu baris arahan sudah memisahkan langkah pertama:

python3 -m json.tool input.json > /dev/null && echo "syntax OK" || echo "invalid syntax"
python3 -c "import json,sys; d=json.load(open(sys.argv[1],encoding='utf-8')); print(type(d).__name__, len(d))" input.json

Baris pertama hanya menjawab «boleh di-parse?», baris kedua baru memeriksa «strukturnya seperti yang dijangka?». Jika ada integer besar, baca dengan Python atau jq, jangan melalui JSON.parse; jika sumber mungkin ber-BOM, nyatakan secara eksplisit encoding='utf-8-sig' ketika membaca daripada mengharap alat melangkaunya sendiri; jika kunci mungkin berulang, imbas secara berasingan sebelum parse, kerana tiada satu pun daripada empat laluan yang mengingatkan. Ditulis ke dalam CI, inilah tepatnya saluran paip yang dibincangkan Editor, Baris Arahan dan Rantaian Alat Automasi, cuma ditambah satu lapis assert kesetiaan data.

6. Cara memilih: jadual perbandingan empat laluan

Laluan Kekuatan Risiko Sesuai untuk
python json.loads / json.tool Ketat, beri baris/lajur dan ofset aksara, integer tidak hilang ketepatan Menolak sifar di hadapan dan BOM, pengekodan perlu dikendali secara eksplisit Semakan kontrak, CI, jejak log
node JSON.parse Asli dalam pelayar, ralat menyertakan petikan teks asal Integer besar dipotong senyap, BOM mencetus ralat Nyahpepijat frontend, alat dalam pelayar
jq -c . Mesra paip, hasil paling pantas Sifar di hadapan dan BOM lolos senyap, tiada ofset aksara Saluran operasi, ambil medan pantas
Alat pelayar /parse, /editor Paparan pokok, isih kunci, salin laluan, tanpa pemasangan Tingkah laku bergantung pada parser halaman, perlu diuji sekali Tinjauan manual, cari ralat sementara

Kuncinya bukan «siapa lebih banyak ciri», tetapi «adakah alat ini berbohong tentang data saya?». Untuk data berisi nombor pesanan, jumlah wang, atau ID peranti, utamakan Python atau alat yang secara jelas mengisytiharkan kesetiaan data; untuk sekadar melihat struktur tanpa simpanan, alat pelayar paling mudah. /parse di laman ini menjalankan semakan sintaks dan format terus dalam pelayar — sesuai untuk «nampak dulu rupanya», tetapi apabila melibatkan integer besar tetap elok disemak semula dengan skrip di atas.

7. Privasi hanyalah salah satu sempadan

«Adakah data saya dimuat naik?» ialah soalan paling kerap untuk alat atas talian, tetapi ia berasingan daripada «adakah data saya diubah?»: alat setempat yang boleh berjalan luar talian tetap boleh memotong integer 20 digit; sebaliknya, alat atas talian yang ralatnya sangat ketat boleh sahaja menghantar teks asal ke pelayan melalui POST. Kedua-duanya perlu disahkan berasingan, tidak boleh saling mengganti. FAQ privasi memberi tiga semakan dua minit; artikel ini melengkapi separuh lagi — dengan senarai sampel di bawah, pastikan alat tidak menukar nilai secara senyap.

8. Senarai semakan kendiri yang boleh disalin

Simpan S1–S10 sebagai cases.txt, satu sampel sebaris, tampal ke mana-mana alat calon, dan padankan tiga perkara: adakah sampel yang patut ralat benar-benar ralat, adakah ralat menyebut kedudukan tertentu, adakah output sampel yang lolos sepadan aksara demi aksara dengan input. Periksa khas empat baris S4, S6, S7, S8 — cukup satu baris SILENT bermakna laluan itu tidak boleh dipakai bersendirian untuk pengesahan data.

Tambah tiga peraturan kejuruteraan: masukkan semakan ketat ke dalam pre-commit hook, jangan tunggu produksi baru menemui integer yang tidak sepadan; tulis fail sebagai UTF-8 tanpa BOM secara konsisten merentas bahasa; bagi JSON daripada pihak ketiga simpan badan respons mentah, dan hasil yang sudah diformat hanya untuk dibaca, bukan untuk ditulis balik.

Soalan lazim

Format berjaya, adakah itu bermakna JSON sudah elok?

Tidak. S4, S6, S7, dan S8 kesemuanya «lulus» format, tetapi sifar di hadapan, BOM, integer 20 digit, dan kunci pendua ditulis semula atau ditelan. Format hanya membuktikan teks diterima oleh sesuatu parser, bukan bahawa nilainya sama dengan yang anda hantar; medan kritikal perlu disahkan nilai demi nilai.

Mengapa jq menerima sifar di hadapan dan BOM yang ditolak Python?

jq ada penganalisis leksikal sendiri, longgar terhadap nombor seperti 01, dan turut melangkau BOM di awal; Python dan Node mematuhi RFC 8259 dengan ketat. Ini bukan pepijat versi, tetapi pilihan antara parser toleran dan parser ketat. Ketika pembersihan yang pertama menjimatkan kerja, ketika semakan kontrak yang kedua wajib.

Bagaimana mengelak integer besar bertukar menjadi 9007199254740992?

Number dalam JavaScript ialah double IEEE 754 dengan had selamat 2^53-1. Membaca ID 20 digit melalui JSON.parse pasti memotongnya. Simpan ID sedemikian sebagai rentetan, atau guna int Python, jq, mahupun pustaka parser yang menyokong BigInt; jika frontend benar-benar perlukan nombor, gantikan medan itu dengan rentetan sebelum parse.

Alat atas talian atau baris arahan setempat, mana satu?

Pisahkan mengikut kepekaan dan tujuan: sekadar melihat struktur atau mencari medan, pelayar paling pantas; masuk saluran paip dan perlukan assert kesetiaan data, guna python -m json.tool bersama skrip semakan ketat. Kedua-duanya elok jalankan S1–S10 dahulu, padankan ralat dan output dengan jangkaan, baru putuskan kelas data apa yang dimasukkan.