Jujukan Teks JSON (RFC 7464): JSON Penstriman Berdelimit RS
Inti pati: RFC 7464 menggunakan pemisah 0x1E untuk menstrim teks JSON yang boleh anda baca dan tulis sekali gus. Meliputi peraturan, cara ia berbeza daripada NDJSON, dan pelaksanaan pisah-kemudian-huraikan.

Kesimpulan utama: gunakan JSON-seq apabila anda mengawal kedua-dua hujung, memerlukan jenis media berdaftar, dan mahu pembaca menyegerak semula selepas pemotongan; gunakan NDJSON apabila data menyuap sistem log, mesti boleh dibaca manusia, atau perlu berfungsi dengan alat sedia ada — jangan sekali-kali memaksa JSON-seq pada rakan yang hanya memahami input berdelimit baris.
NDJSON memisahkan rekod pada baris baru, tetapi baris baru ialah konvensyen manusia. RFC 7464 menulis sempadan ke dalam strim bait itu sendiri: 0x1E yang tidak kelihatan sebelum setiap rekod.
1. Masalah yang diselesaikan oleh satu bait
RFC 7464 diterbitkan pada Februari 2015 dengan tajuk JSON Text Sequences, dan ia mendaftarkan jenis media application/json-seq. Ia tidak menambah jenis data baharu. Ia hanya mentakrifkan bagaimana jujukan teks JSON disusun dalam satu strim bait. Keseluruhan peraturan muat dalam satu ayat: letakkan satu RS sebelum setiap teks JSON dan satu LF selepasnya.
- RS ialah pemisah rekod ASCII, titik kod
0x1E, dikenali dalam Unicode sebagai INFORMATION SEPARATOR TWO (U+001E); - LF ialah suapan baris,
0x0A.
Dua rekod kelihatan seperti ini pada peringkat bait:
1E 7B 22 69 64 22 3A 31 7D 0A ← 0x1E {"id":1} + LF
1E 7B 22 69 64 22 3A 32 7D 0A ← 0x1E {"id":2} + LF
RS terletak sebelum rekod, yang bertentangan dengan susun atur NDJSON "tulis kandungan, kemudian tulis pemisah". Arah itu mengubah tingkah laku pemulihan: pembaca yang melihat 0x1E tahu rekod sebelumnya telah berakhir, jadi walaupun rekod yang dipotong di tengah-tengah penulisan tidak boleh mengaburkan sempadan rekod seterusnya.
Ini bukan reka bentuk teori. RFC secara eksplisit menyebut rekod yang ditambah ke fail log yang dipotong oleh sistem fail selepas ranap atau pembunuhan pentadbiran, dan ia menyatakan penghurai tidak sepatutnya membatalkan keseluruhan bacaan kerana itu.
2. Dua set peraturan: pengekod dan penghurai
RFC 7464 luar biasa kerana menyediakan dua tatabahasa ABNF, satu yang mengekang penulis dan satu yang melonggarkan pembaca.
| Peranan | Tatabahasa | Maksud |
|---|---|---|
| Pengekod | *(RS JSON-text LF) |
Satu RS sebelum setiap teks JSON, satu LF selepasnya |
| Penghurai | *(1*RS possible-JSON) |
Bait RS berturutan dibenarkan, dan LF antara rekod adalah pilihan |
Dalam istilah kejuruteraan, terdapat lima peraturan keras:
- UTF-8 adalah wajib; UTF-16 dan UTF-32 tidak boleh digunakan;
- Penghurai menganggap "apa-apa bait selain RS" sebagai teks JSON yang mungkin dan cuba menghuraikannya; pada kegagalan ia melangkau ke RS seterusnya dan teruskan;
- Bait RS berturutan tidak mewakili elemen kosong dan diabaikan;
- Tiada penanda penghujung jujukan, jadi strim hanya berakhir;
- Apabila nilai peringkat atas ialah nombor,
true,falseataunull, rekod mesti berakhir dengan bait ruang putih, jika tidak ia digugurkan sebagai mungkin dipotong.
Peraturan kelima ialah yang orang lupa. Objek, tatasusunan dan rentetan adalah berpemisah sendiri dalam JSON; nombor tidak. <RS>123<RS> tidak boleh memberitahu anda sama ada ia mengandungi 123 yang lengkap atau 1234 yang dipotong. Oleh itu RFC mewajibkan penulis menambah LF, menggunakannya sebagai bait kenari untuk pengesanan pemotongan. Begitu juga <RS>true<RS> mungkin pada asalnya trueish.
3. Memilih antara JSON-seq dan NDJSON
Kedua-dua format menstrim, tetapi mereka menyasarkan masalah berbeza:
| Dimensi | Jujukan Teks JSON | NDJSON / JSON Lines |
|---|---|---|
| Kedudukan pemisah | RS (0x1E) sebelum rekod |
LF (0x0A) selepas rekod |
| Jenis media | application/json-seq, berdaftar IANA |
Tiada pendaftaran rasmi, konvensyen komuniti |
| Kebolehan dibaca bait | Aksara kawalan tidak kelihatan, cat tidak menunjukkan apa-apa |
Teks biasa, terus boleh dibaca |
| Sisipan selari | Pembaca boleh menyegerak semula dan menggugurkan hujung yang dipotong | Sisipan juga berfungsi, baris rosak dilangkau |
| Nombor peringkat atas | Memerlukan bait ruang putih di hujung untuk dipercayai | Suapan baris ialah sempadan |
| Pemisah di dalam rentetan | RS hanya boleh muncul sebagai \u001e |
LF mesti ditulis sebagai \n |
| Perkakasan | jq --seq dan beberapa pustaka |
Hampir setiap alat log dan data besar |
| Sambungan fail | Tidak ditakrifkan oleh RFC | .jsonl / .ndjson |
Keputusannya ringkas: gunakan JSON-seq apabila anda mengawal kedua-dua hujung, mahukan jenis media formal, dan perlu pembaca menyegerak semula selepas pemotongan; gunakan JSON Lines dan NDJSON apabila data menyuap sistem log, perlu dibaca manusia, atau mesti berfungsi dengan alat sedia ada.
4. Penghurai: pisah pada 0x1E, kemudian JSON.parse
Manfaat paling nyata RFC 7464 ialah anda tidak memerlukan penghurai JSON penstriman. Pisahkan mengikut bait, kemudian panggil JSON.parse pada setiap segmen:
/** Pemisah rekod ASCII, ditakrifkan oleh RFC 7464 sebagai 0x1E */
const RECORD_SEPARATOR = 0x1e;
/**
* Menghuraikan satu ketulan bait Jujukan Teks JSON.
* @param buffer Bait yang telah dibaca setakat ini
* @returns Rekod yang berjaya dihuraikan dan bilangan rekod yang gagal
*/
function parseJsonSequence(buffer: Buffer): { records: unknown[]; badCount: number } {
const records: unknown[] = [];
let badCount = 0;
let recordStart = buffer.indexOf(RECORD_SEPARATOR);
while (recordStart !== -1) {
const nextRecordStart = buffer.indexOf(RECORD_SEPARATOR, recordStart + 1);
const recordEnd = nextRecordStart === -1 ? buffer.length : nextRecordStart;
const rawText = buffer.subarray(recordStart + 1, recordEnd).toString('utf8').trim();
if (rawText !== '') {
try {
records.push(JSON.parse(rawText));
} catch {
badCount += 1;
}
}
recordStart = nextRecordStart;
}
return { records, badCount };
}
Tiga nota pelaksanaan:
- Pemisahan pada peringkat bait adalah selamat. Bait sambungan UTF-8 berada dalam julat
0x80–0xBF, jadi ia tidak pernah boleh sama dengan0x1E. Memisahkan pada 0x1E tidak boleh memotong aksara berbilang bait separuh. - Penampan hujung semasa menstrim. Semasa membaca dalam ketulan, segmen akhir mungkin belum sampai ke RS seterusnya. Kekalkannya dalam penampan sehingga ketulan seterusnya tiba; jika tidak anda akan menghuraikan separuh rekod yang sama dua kali.
- Kira kegagalan dan bukannya membatalkan. RFC memberitahu penghurai untuk melangkau ke RS seterusnya dan teruskan, jadi
badCountialah metrik amaran, bukan pengecualian. Rekod akhir yang dipotong juga tergolong dalam baldi itu.
Penggunaan memori bergantung pada rekod tunggal terbesar tambah satu penampan, bukan saiz keseluruhan fail. Itulah idea yang sama yang dibincangkan dalam penghuraian dan penstriman JSON besar.
5. Aksara kawalan di dalam rentetan
JSON mewajibkan setiap aksara kawalan dari U+0000 hingga U+001F dilepaskan di dalam rentetan, dan RS tergolong dalam julat itu. Oleh itu rentetan JSON yang sah hanya boleh mengandungi lima aksara \u001e, tidak pernah bait 0x1E mentah. Itulah sebabnya memisahkan pada 0x1E sentiasa selamat untuk data yang sah.
Bahaya terletak di pihak penulis:
- Membina teks JSON dengan tangan menggunakan
'\x1e', memasukkan aksara kawalan mentah ke dalam rentetan; - Membina JSON dengan literal templat dan bukannya
JSON.stringify, meninggalkan baris baru, tanda petik dan aksara kawalan tanpa dilepaskan; - Menyalin teks yang mengandungi RS daripada terminal atau editor, di mana aksara kawalan ditelan atau diganti.
Corak yang betul sentiasa menjana teks dahulu, kemudian melampirkan pemisah:
const recordText = JSON.stringify(record);
const line = `${String.fromCharCode(RECORD_SEPARATOR)}${recordText}\n`;
JSON.stringify membebaskan aksara kawalan sebagai \u00XX, jadi RS mentah tidak pernah boleh muncul di dalam rekod. Sebaik sahaja langkah ini betul, logik pemisahan hanya perlu mengendalikan bait dan tidak pernah perlu memahami sintaks JSON.
Kebanyakan kegagalan dalam kategori ini muncul sebagai JSON.parse melontar Unexpected token, terutamanya kes mengelirukan di mana "aksara pertama ialah bait kawalan yang tidak kelihatan". Jadual rujukan mesej ralat JSON membincangkan kelas ralat itu baris demi baris.
6. Pengangkutan HTTP dan jenis media
application/json-seq ialah jenis media berdaftar tanpa parameter wajib atau pilihan, dan set aksara ditetapkan kepada UTF-8. Pada HTTP, tiga amalan penting:
- Hantar
Accept: application/json-seqdan balas denganContent-Type: application/json-seq; - Untuk menstrim secara sebenar, pelayan mesti menggunakan pemindahan berketul dan melumpuhkan penampan respons dalam perantara seperti proksi songsang, CDN dan alat ujian beban;
- Pelanggan yang membaca ketulan demi ketulan mungkin menerima ketulan yang berakhir di tengah-tengah rekod, jadi penghurai mesti menampan sehingga RS seterusnya sebelum mengeluarkan rekod.
Jika pihak lain hanya memahami input berdelimit baris, jangan paksa JSON-seq padanya. Ia standard, tetapi penembusan ekosistemnya jauh di bawah NDJSON, jadi keserasian harus diputuskan semasa rundingan protokol dan bukannya selepas kegagalan penghuraian.
7. Baris arahan, log dan sokongan alat
jq ialah salah satu daripada beberapa alat dengan sokongan asli; bendera --seq-nya wujud tepat untuk RFC 7464:
printf '\x1e{"id":1}\n\x1e{"id":2}\n' | jq --seq -c '.id'
Itu mencetak 1 dan 2 pada baris berasingan. Untuk titik akhir penstriman, ingat untuk melumpuhkan penampan curl:
curl -sN https://api.example.com/events | jq --seq -c '.type'
jq --seq membaca input RFC 7464, mengabaikan rekod rosak, dan terus menghuraikan. Itu sangat berguna semasa mengekori fail log yang masih ditulis: rekod terakhir mungkin separuh ditulis, dan output tidak akan menunjukkan objek akhir palsu.
7.1 Sokongan asli mengikut persekitaran
| Persekitaran | Sokongan terbina dalam | Nota |
|---|---|---|
| jq | --seq |
Pilihan baris arahan paling mudah |
| Node.js | Tiada | Buffer.indexOf ditambah JSON.parse |
| Python | Tiada | Pisah pada b'\x1e' |
| Go | Tiada | SplitFunc tersuai untuk bufio.Scanner |
| Pelayar | Tiada | fetch ditambah ReadableStream, pisahkan bait secara manual |
7.2 Perangkap biasa
Kesilapan berkumpul di pihak pembacaan:
- Membaca dengan
readline, yang meninggalkan awalan RS tidak kelihatan pada baris pertama sehinggaJSON.parsesentiasa gagal; - Membuka fail
.json-seqdalam editor di mana aksara kawalan tidak kelihatan, jadi ia kelihatan seperti satu dokumen JSON pelik; - Memisahkan dengan
split(/\n/), yang mengelirukan baris baru yang dilepaskan di dalam rekod dengan LF antara rekod; - Menganggap RS sebagai aksara biasa dalam dokumentasi, jadi ia hilang selepas salin dan tampal.
8. Bila ia berbaloi digunakan
Sesuai:
- Anda memiliki kedua-dua pengeluar dan pengguna, tanpa alat pihak ketiga di antaranya;
- Data ialah strim peristiwa yang ditambah berterusan dan proses mungkin ranap, jadi anda memerlukan pemulihan daripada pemotongan;
- Protokol itu layak mendapat jenis media formal dan bukannya konvensyen seperti
.jsonl; - Rekod individu mungkin nombor atau boolean peringkat atas, di mana peraturan pemotongan eksplisit penting.
Kurang sesuai:
- Log mesti masuk ke ELK, Loki atau sistem lain yang hanya memahami input berdelimit baris;
- Orang ramai perlu
cat,grepdanawkdata secara terus; - Pihak lain hanya boleh menghuraikan
application/json.
9. Senarai semak
- [ ] Penulis menjana rekod dengan
JSON.stringify, kemudian menambah0x1Edi hadapan dan\ndi hujung; - [ ] Rekod yang mengandungi nombor, boolean atau
nullperingkat atas berakhir dengan LF; - [ ] Pembaca memisahkan pada bait dan bukannya membaca baris demi baris;
- [ ] Bacaan penstriman menyimpan hujung yang belum tertutup dalam penampan sehingga RS seterusnya tiba;
- [ ] Kegagalan penghuraian dikira dan dilangkau, tidak pernah mematikan jujukan;
- [ ] Antara muka awam mengisytiharkan
application/json-seqsecara eksplisit dan mengekalkan sandaran NDJSON.
Soalan Lazim
Patutkah saya menggunakan JSON-seq untuk titik akhir API awam?
Hanya apabila kedua-dua hujung boleh diubah atau format dirundingkan lebih awal. JSON-seq ialah standard berdaftar dengan jenis media application/json-seq, tetapi penembusan ekosistemnya jauh di bawah NDJSON, jadi memaksanya pada pelanggan pihak ketiga biasanya gagal. Putuskan keserasian semasa rundingan protokol dan kekalkan sandaran NDJSON.
Patutkah saya menukar saluran NDJSON sedia ada kepada JSON-seq?
Biasanya tidak. NDJSON sudah menstrim, menambah dan melangkau baris rosak, dan hampir setiap alat log dan data besar menerimanya. Tukar hanya apabila anda memerlukan jenis media berdaftar IANA atau penyegerakan semula yang diberikan oleh pemisah di hadapan, dan hanya apabila pengeluar serta pengguna boleh diubah bersama-sama.
Mana lebih biasa, Jujukan Teks JSON atau NDJSON?
NDJSON. Peraturannya lebih ringkas, liputan alatnya jauh lebih luas, dan sistem log serta saluran data besar umumnya menerimanya. JSON-seq menang pada jenis media berdaftar IANA dan pada penyegerakan semula yang datang daripada meletakkan pemisah di hadapan, yang sesuai untuk protokol penstriman terkawal dan bukannya pertukaran data umum.
Mengapa pemisah diletakkan sebelum rekod dan bukannya selepasnya?
Kerana rekod boleh dipotong. Dengan pemisah di hadapan, pembaca yang melihat RS tahu rekod baharu telah bermula, jadi rekod sebelumnya tidak boleh merosakkan yang seterusnya sama ada ia lengkap atau tidak. Dengan pemisah di hujung, penulisan yang terganggu meninggalkan segmen akhir yang tiada penanda penghujung dan tiada penanda permulaan berikut, jadi sempadan hanya boleh diteka.
Apa yang berlaku jika 0x1E muncul di dalam rekod?
Rentetan JSON yang sah mesti melepaskan RS sebagai \u001e, jadi 0x1E mentah tidak sepatutnya muncul. Jika ia muncul, teks itu tidak lagi JSON yang sah: memisahkan pada 0x1E akan terputus di tempat yang salah dan JSON.parse akan gagal. Pembaikan tergolong di pihak penulis: gunakan JSON.stringify dan bukannya teks yang dibina dengan tangan.
Bolehkah pelayar menghuraikan application/json-seq secara terus?
Tidak. Pelayar tidak menyertakan penghurai JSON-seq; fetch hanya menyampaikan bait. Anda perlu menggunakan response.body.getReader(), mengumpul ketulan sehingga 0x1E seterusnya, dan memanggil JSON.parse pada setiap segmen. Untuk muatan kecil, membaca keseluruhan badan sebagai rentetan dahulu dan memisahkannya selepas itu juga berfungsi.
Mengapa nombor peringkat atas memerlukan LF di hujung?
Objek, tatasusunan dan rentetan berpemisah sendiri dalam JSON, manakala nombor tidak: <RS>123<RS> mungkin 123 yang lengkap atau 1234 yang dipotong. RFC 7464 oleh itu mewajibkan satu bait ruang putih selepas setiap rekod, dan LF bertindak sebagai kenari untuk pengesanan pemotongan. Peraturan yang sama terpakai kepada true, false dan null peringkat atas.