Menukar JSON kepada dan daripada CSV serta Data Jadual
Inti pati: CSV bersifat dua dimensi, JSON berbentuk pokok. Bahagian sukar penukaran bukan kod — ia ialah memutuskan lebih awal bagaimana tatasusunan diratakan dan bagaimana jenis dikekalkan.

Kesimpulan utama: ratakan dengan kunci bertitik dan tatasusunan yang diserialkan JSON apabila orang akan membaca eksport itu, pecahkan kepada jadual induk serta jadual berkaitan apabila memuatkan pangkalan data, dan kembangkan tatasusunan menjadi baris tambahan hanya untuk pangsi; semasa memulihkan, tukar mengikut definisi lajur eksplisit dan bukan inferens global.
Meratakan pokok menjadi jadual sentiasa kehilangan hierarki; memulihkan jadual menjadi pokok sentiasa meneka jenis. Kedua-dua arah memerlukan peraturannya dinyatakan lebih awal.
1. Apa yang sebenarnya ditetapkan oleh CSV
RFC 4180 hanya beberapa halaman dan menetapkan lebih sedikit daripada yang diandaikan ramai orang:
- Setiap rekod menempati satu baris, dengan medan dipisahkan oleh koma;
- Penamat baris ialah CRLF, walaupun dalam amalan
\njuga diterima; - Medan yang mengandungi koma, tanda petikan berganda, atau baris baharu mesti dibaluti dengan tanda petikan berganda;
- Tanda petikan berganda di dalam medan ditulis sebagai dua tanda petikan berganda
""; - Baris pertama boleh menjadi pengepala;
- Tiada pengekodan aksara ditetapkan, dan jenis juga tidak.
id,name,remark
1,张三,"喜欢 JSON,讨厌尾逗号"
2,李四,"他说:""这行有引号"""
3,王五,"多行
备注"
Perhatikan peraturan 3: medan yang mengandungi baris baharu adalah sah, jadi penghurai naif yang memisahkan pada \n dan kemudian pada , dijamin salah.
2. JSON → CSV: tentukan struktur dahulu
Hanya ada tiga strategi untuk meratakan struktur pokok menjadi jadual dua dimensi:
Strategi 1: ratakan medan (paling lazim)
{ "id": 1, "user": { "name": "张三", "city": "杭州" }, "tags": ["a", "b"] }
id,user.name,user.city,tags
1,张三,杭州,"[""a"",""b""]"
- Gabungkan kunci objek bersarang dengan
.; - Tatasusunan tidak boleh dinyatakan sebagai lajur, jadi
JSON.stringify-kannya terus ke dalam sel (yang masih medan CSV yang sah); - Kelemahan: alat hamparan tidak boleh menapis tatasusunan itu, jadi penghuraian kedua diperlukan.
Strategi 2: kembangkan tatasusunan menjadi berbilang baris
id,user.name,tag
1,张三,a
1,张三,b
Ini sesuai untuk hubungan satu-ke-banyak di mana pengguna hiliran mengagregat mengikut elemen. Kelemahannya ialah medan induk yang berganda dan bilangan baris yang membengkak.
Strategi 3: pecahkan kepada berbilang jadual
Jadual utama ditambah jadual berkaitan yang disambung oleh id — pendekatan paling bersih, tetapi ia melangkaui "satu eksport" dan lebih menyerupai pemodelan data.
Yang mana satu? Gunakan strategi 1 untuk eksport yang ditujukan kepada manusia, strategi 3 untuk memuatkan ke dalam pangkalan data, dan strategi 2 untuk jadual pangsi.
3. CSV → JSON: bagaimana jenis diteka
CSV tiada jenis, jadi setiap langkah pemulihan ialah tekaan:
| Kandungan sel | Makna mungkin | Risiko menukar terus kepada nombor |
|---|---|---|
00123 |
ID pekerja, kod pos | Sifar di hadapan hilang → 123 |
1.10 |
Nombor versi, jumlah | Menjadi 1.1 |
1e5 |
Pengecam atau notasi saintifik | Menjadi 100000 |
2026-10-30 |
Tarikh atau rentetan | Kekaburan dalam zon waktu dan format |
13800138000 |
Nombor telefon | Masih selamat pada 11 digit, tetapi sesetengah ID memang kehilangan ketepatan |
"" |
Rentetan kosong atau null | Kekaburan semantik |
Lalai yang disyorkan: layan segala-galanya sebagai rentetan, dan biarkan skema atau definisi lajur eksplisit menentukan lajur mana yang menjadi nombor atau boolean. "Inferens pintar" menyeronokkan semasa mengeksport dan punca insiden semasa mengimport.
type ColumnType = 'string' | 'number' | 'boolean' | 'json';
/**
* Converts CSV rows into objects according to the column definitions.
* @param rows The raw row array; the first row is the header
* @param columnTypes Mapping from column name to type
* @returns The converted array of objects
*/
function rowsToRecords(
rows: string[][],
columnTypes: Record<string, ColumnType>,
): Array<Record<string, unknown>> {
const [headerRow, ...dataRows] = rows;
return dataRows.map((dataRow) => {
const record: Record<string, unknown> = {};
headerRow.forEach((columnName, columnIndex) => {
const rawValue = dataRow[columnIndex] ?? '';
const columnType = columnTypes[columnName] ?? 'string';
if (columnType === 'number') {
const numericValue = Number(rawValue);
record[columnName] = Number.isFinite(numericValue) ? numericValue : null;
return;
}
if (columnType === 'boolean') {
record[columnName] = rawValue === 'true' || rawValue === '1';
return;
}
if (columnType === 'json') {
try {
record[columnName] = JSON.parse(rawValue);
} catch {
record[columnName] = rawValue;
}
return;
}
record[columnName] = rawValue;
});
return record;
});
}
4. Tiga perangkap berkaitan Excel
4.1 Pengekodan
Apabila Excel membuka CSV UTF-8 tanpa BOM, teks Cina bertukar menjadi mojibake. Menambah BOM (\uFEFF) semasa mengeksport ialah pembetulan keserasian paling murah:
const bom = '\uFEFF';
const csvContent = `${bom}${headerLine}\n${bodyLines.join('\n')}`;
4.2 Nombor panjang bertukar menjadi notasi saintifik
ID negara 18 digit atau nombor pesanan 19 digit muncul dalam Excel sebagai 1.23457E+17, dan nilai itu sendiri sudah dipotong.
Menambah tab di hadapan medan atau membalutnya sebagai formula (="1234...") mengekalkan format teks, tetapi menukar sel itu menjadi formula.
Pendekatan yang lebih selamat ialah memberitahu pengguna untuk "menggunakan ciri import dan menetapkan lajur itu sebagai teks", atau menawarkan .xlsx bersama eksport itu.
4.3 Pemisah terjejas oleh lokasi
Sesetengah lokasi Eropah menggunakan titik bertindih ; sebagai pemisah lalai. Apabila mengedarkan merentas wilayah, sama ada tetapkan CSV kepada koma dan nyatakannya, atau tawarkan versi TSV (dipisahkan tab) — TSV lebih toleran terhadap lokasi.
5. Mengendalikan fail besar
Jangan muatkan CSV yang mempunyai lebih daripada seratus ribu baris ke dalam memori sekali gus:
import { createReadStream } from 'node:fs';
import { parse } from 'csv-parse';
const parser = createReadStream('orders.csv').pipe(
parse({ columns: true, skipEmptyLines: true, bom: true }),
);
for await (const record of parser) {
// Process one record at a time; memory usage is independent of file size
}
Arah eksport berfungsi dengan cara yang sama: jana dan tulis ke aliran semasa anda pergi, dan bukan menggabungkan satu rentetan gergasi.
6. Semakan kendiri konsistensi perjalanan pergi balik
Perjalanan pergi balik CSV (JSON → CSV → JSON) hampir tidak pernah betul-betul serupa, tetapi anda boleh menetapkan kehilangan mana yang boleh diterima:
| Semakan | Jangkaan |
|---|---|
| Bilangan medan | Serupa |
| Nilai rentetan | Serupa (termasuk sifar di hadapan) |
| Jenis berangka | Ditentukan oleh definisi lajur, bukan oleh inferens |
| Objek bersarang | Boleh dipulihkan melalui laluan bertitik |
| Tatasusunan | Kelihatan selepas penyerialan kepada rentetan JSON |
| Nilai kosong | Dinormalkan kepada null atau rentetan kosong — pilih satu sahaja |
Meletakkan jadual ini ke dalam ujian jauh lebih berkesan daripada persetujuan lisan.
7. Senarai semak
- [ ] Semantik "satu baris ialah satu rekod" dinyatakan secara eksplisit dan tercermin dalam nama fail eksport;
- [ ] Setiap lajur yang mungkin kehilangan ketepatan dieksport sebagai rentetan;
- [ ] CSV Cina menyertakan BOM, atau pengekodan dinyatakan secara eksplisit;
- [ ] Medan yang mengandungi koma, petikan, atau baris baharu di-escape dengan betul (gunakan pustaka, jangan gabungkan rentetan sendiri);
- [ ] Fail besar melalui bacaan dan tulisan penstriman;
- [ ] Import menukar jenis mengikut definisi lajur, tidak pernah melalui inferens pintar global.
Soalan Lazim
Bagaimana saya memilih antara meratakan, mengembangkan baris, dan memecahkan jadual?
Ratakan untuk eksport yang ditujukan kepada manusia: gabungkan kunci bersarang dengan . dan JSON.stringify tatasusunan ke dalam sel, dengan kos tidak dapat menapis tatasusunan itu dalam hamparan. Pecahkan kepada jadual induk ditambah jadual berkaitan apabila sasarannya ialah pangkalan data. Kembangkan tatasusunan menjadi baris tambahan apabila pengguna hiliran mengagregat mengikut elemen, dengan menerima medan induk yang berganda.
Mengapa import CSV tidak sepatutnya membuat inferens jenis secara automatik?
RFC 4180 menetapkan pemisah dan escaping tetapi tiada jenis, jadi setiap langkah pemulihan ialah tekaan: 00123 kehilangan sifar di hadapannya, 1.10 menjadi 1.1, dan 1e5 menjadi 100000. Layan segala-galanya sebagai rentetan secara lalai dan biarkan skema atau definisi lajur eksplisit menentukan lajur mana yang menjadi nombor atau boolean.
Patutkah saya menjangkakan perjalanan pergi balik CSV menghasilkan semula JSON asal dengan tepat?
Hampir tidak pernah. Tetapkan kehilangan yang boleh diterima sebagai ganti: bilangan medan serupa, nilai rentetan serupa termasuk sifar di hadapan, jenis berangka diputuskan oleh definisi lajur, objek bersarang boleh dipulihkan melalui laluan bertitik, tatasusunan kelihatan selepas penyerialan, dan nilai kosong dinormalkan kepada null atau rentetan kosong — pilih satu sahaja.