JSON ↔ CSV: cột là gì, flatten thế nào (hiểu nhanh trước khi convert)
CSV = bảng phẳng (hàng × cột). JSON = cây. Convert chỉ “đúng” khi bạn quyết được: một hàng là object nào, và object lồng nhau flatten thành cột ra sao. Công cụ JSON ↔ CSV tự làm phẳng object bằng a.b, nhưng không bung mảng con thành nhiều hàng — hiểu quy tắc này trước khi paste Excel.
Sinh viên, freelancer và team outsourcing ở Việt Nam hay nhận file JSON từ Postman rồi hỏi: “Mở Excel thế nào?” Paste thẳng vào sheet thì vỡ. Convert online mà không hiểu cột và flatten thì header thành undefined, hoặc mất hết address.city.
Bài này không so sánh “khi nào chọn JSON hay CSV cho SME” dài dòng. Tập trung khái niệm cột + flatten ngắn — đủ để bạn đoán trước bảng sẽ ra sao trước khi bấm nút.
Ai cần đọc
- Dev junior lần đầu xuất API response ra spreadsheet
- Người nhận JSON từ đối tác và phải giao CSV cho kế toán
- Ai từng thấy lỗi “Vui lòng nhập mảng JSON” mà không hiểu vì sao
Cột CSV là gì (nhìn từ JSON)
CSV không có object, không có mảng lồng. Chỉ có:
- Dòng 1 = tên cột (header)
- Mỗi dòng sau = một bản ghi
- Ô = giá trị text (có thể bọc
"nếu chứa dấu phẩy)
Ánh xạ tư duy:
| Trong JSON | Trong CSV |
|---|---|
| Phần tử trong array | Một hàng |
| Key ở cấp lá (sau flatten) | Một cột |
| Giá trị primitive / chuỗi hóa | Một ô |
Ví dụ tối thiểu — hai object, cùng bộ key:
[
{"name": "An", "city": "Đà Nẵng"},
{"name": "Bình", "city": "Huế"}
]
→ Header: name,city — 2 cột, 2 hàng dữ liệu. Không cần flatten.
Nếu object thiếu key:
[
{"name": "An", "city": "Đà Nẵng"},
{"name": "Bình"}
]
→ Vẫn 2 cột (name, city). Hàng của Bình: ô city trống. Header là hợp mọi key xuất hiện trong mảng — đây là lý do bảng “phình” khi từng phần tử schema khác nhau.
Flatten ngắn: object lồng → cột dấu chấm
JSON hay có cây:
[
{
"id": 1,
"user": { "name": "Lan", "email": "[email protected]" },
"meta": { "source": "web" }
}
]
CSV không giữ user là object. Flatten phổ biến (và đúng với công cụ Kawa):
| Cột | Giá trị |
|---|---|
id | 1 |
user.name | Lan |
user.email | [email protected] |
meta.source | web |
Quy tắc nhớ: mỗi đường dẫn tới lá = một cột. Độ sâu càng nhiều, header càng dài — vẫn đọc được trong Excel, nhưng pivot/filter theo user.* thì bạn phải quen tên cột có dấu chấm.
Object lồng vs mảng con
| Cấu trúc | Sau flatten (kiểu Kawa) | Ý nghĩa |
|---|---|---|
user: { name } | Cột user.name | An toàn, 1:1 theo đường dẫn |
tags: ["a","b"] | Một ô (chuỗi mảng) | Không tách thành nhiều cột/hàng |
items: [{ sku }] | Một ô JSON string | Không tự bung thành N hàng đơn hàng |
Lỗi hay gặp: muốn “mỗi dòng = một dòng đơn hàng trong items[]” nhưng lại convert cả object order. Kết quả: một hàng order, cột items chứa cả mảng. Đúng hướng: lấy order.items (mảng) làm input — mỗi phần tử items mới là một hàng.
Vì sao phải là mảng?
Công cụ báo “Vui lòng nhập mảng JSON” khi bạn dán:
{ "id": 1, "name": "An" }
Object đơn = một bản ghi, nhưng không có “danh sách hàng”. Bọc lại:
[{ "id": 1, "name": "An" }]
API kiểu bọc:
{ "data": [ { "id": 1 }, { "id": 2 } ], "total": 2 }
→ Chỉ convert phần data. total không phải hàng — đừng ép cả object gốc thành CSV.
JSON → CSV trên UI (sau khi đã hiểu cột)
- Mở JSON ↔ CSV (chạy trên trình duyệt, không cần đăng ký)
- Dán mảng object (đã lấy đúng cấp hàng)
- Bấm JSON → CSV
- Đọc dòng header: có đủ
user.namekhông? Có cột “rác” từ key lệch schema không? - Sao chép kết quả → Excel / Google Sheets
Checklist 30 giây trước khi gửi file cho người khác:
- Số hàng dữ liệu = số phần tử mảng?
- Header có tên ổn định (không đổi mỗi lần vì object[0] thiếu key)?
- Tiếng Việt trong ô còn đúng (UTF-8) khi mở bằng Sheets?
- Không lộ PII nếu bạn share link tool trên máy công cộng — dữ liệu không upload, nhưng clipboard/màn hình vẫn lộ
CSV → JSON: cột trở lại key phẳng
Chiều ngược: dòng header thành tên property. Với header user.name, nhiều tool (kể cả convert đơn giản) tạo key đúng chuỗi "user.name" — không tự dựng lại { user: { name } }.
id,user.name,active
1,Lan,true
→ Thường ra:
[
{ "id": "1", "user.name": "Lan", "active": "true" }
]
Hai điểm quan trọng:
- Kiểu:
"1"và"true"là string — API cần number/boolean thì phải ép trong code - Cấu trúc: muốn nested thật sự thì cần bước unflatten riêng, hoặc đổi header thành key phẳng cố ý (
user_name)
Đây là lý do round-trip JSON→CSV→JSON hiếm khi bằng bản gốc từng byte.
Case ngắn: response “đẹp” nhưng cột nổ
Freelancer nhận:
[
{
"orderId": "VN-100",
"customer": { "name": "Minh", "phone": "09x" },
"items": [
{ "sku": "A1", "qty": 2 },
{ "sku": "B2", "qty": 1 }
]
}
]
Convert cả object → 1 hàng, cột items dài. Kế toán cần 2 hàng sản phẩm.
Cách đúng khái niệm cột: flatten/export cấp items, gắn thêm orderId vào mỗi phần tử trước (map trong code hoặc chỉnh JSON tay):
[
{ "orderId": "VN-100", "sku": "A1", "qty": 2 },
{ "orderId": "VN-100", "sku": "B2", "qty": 1 }
]
→ 3 cột rõ, 2 hàng — đúng mô hình CSV.
Khi nào chưa nên convert
- Schema mỗi phần tử khác nhau hoàn toàn → header hàng trăm cột thưa
- Cần giữ hierarchy sâu để app đọc lại → giữ JSON, chỉ export subset
- File cực lớn (chục MB) trên tab trình duyệt yếu → script Node/Python ổn định hơn
Còn lại: hiểu cột = key lá, hàng = phần tử mảng, rồi dùng JSON ↔ CSV là đủ cho đa số bài tập, ticket hỗ trợ và bàn giao nhanh với non-dev.