JSON Toolkit

JSON sang CSV: header đến từ đâu và vì sao giá trị bị thay đổi

Kết luận: Chín thí nghiệm trên một bộ chuyển đổi thật: chiến lược "khóa của dòng đầu tiên" mất trường trong im lặng, null, vắng mặt và chuỗi rỗng đều thành ô trống, số nguyên 20 chữ số mất độ chính xác ngay ở JSON.parse, và dấu chấm trong khóa khiến hai đường dẫn khác nhau va chạm trong cùng một cột. Header được xây thế nào, giá trị nào bị bóp méo và cách chọn chiến lược theo mục đích phía sau.

Nâng caoĐọc khoảng 6 phútXuất bản 2026-11-04Cập nhật 2026-11-04
Chiến lược header và độ trung thực giá trị khi chuyển đổi JSON sang CSV

Khi chuyển đổi JSON sang CSV mất dữ liệu, gần như chưa bao giờ điều đó xảy ra ở bước "chuyển đổi". Sự mất mát nằm trong hai câu hỏi mà chẳng ai hỏi: ai đã quyết định header, và liệu mỗi ô còn nhận ra được nó chính là giá trị thuở ban đầu?

Một trường biến mất trong ba mươi giây

Nhiều bộ chuyển đổi — kể cả pipeline jq kinh điển — lấy header chỉ từ bản ghi đầu tiên. Tái hiện hậu quả bằng Python:

data = [{"id": 1, "name": "A"}, {"id": 2, "email": "b@x.com"}]
first_keys = list(data[0].keys())          # ['id', 'name']
rows = [[r.get(k, "") for k in first_keys] for r in data]
# Email của bản ghi thứ hai biến mất mà không có lỗi hay cảnh báo

Khóa email không có trong header: nó thậm chí không có quyền trở thành ô trống — cột chưa từng được tạo, và giá trị bị vứt đi trong im lặng. Cách khắc phục là hợp nhất khóa: duyệt qua tất cả bản ghi và tạo một cột cho mỗi khóa xuất hiện ở bất cứ đâu. Cùng dữ liệu đó đi qua hợp nhất sẽ cho header id,name/email, với ô trống ở chỗ thiếu name — và chẳng mất gì.

Bộ chuyển đổi trực tiếp trên trang /convert/csv của chúng tôi áp dụng đúng chiến lược này. Với đầu vào [{"id":1,"name":"A"},{"id":2,"email":"b@x.com"}], nó xuất ra:

id,name,email
1,A,
2,,b@x.com

Các khóa được sắp theo lần xuất hiện đầu tiên, trường vắng mặt thành ô trống. jsonoperations và jsonbeam đều gọi đây là "bản vá cho lỗi kinh điển" — nhưng "dùng hợp nhất" mới chỉ là nửa câu chuyện. Hợp nhất có cái giá riêng; đó là phần tiếp theo.

Ba cách xây header — và mỗi cách hợp với ai

Chiến lược header Cách xây Sức mạnh Cái giá
Khóa của dòng đầu tiên Chỉ đọc bản ghi đầu tiên Ít cột, ổn định, một dòng jq Trường mới của các bản ghi sau mất trong im lặng
Hợp nhất khóa Đọc tất cả bản ghi Không mất trường nào Cột thưa và nhiều ô trống khi cấu trúc đa dạng
Danh sách cột tường minh Định nghĩa tay hoặc theo schema Kiểm soát hoàn toàn, tái lập được Phải bảo trì; trường mới đòi cập nhật tay

Tiêu chí chọn không phải "cái nào tinh vi hơn" mà là bên nhận sẽ làm gì với tệp CSV này:

  • Đồng nghiệp liếc qua: khóa dòng đầu là đủ, cấu trúc bản ghi thường đồng nhất.
  • Báo cáo hay pipeline dữ liệu: hợp nhất khóa là sàn — ô trống tự thân cũng là thông tin.
  • Xuất tự động định kỳ: danh sách cột tường minh. Trôi cấu trúc (thêm trường phía trước, đổi tên khóa) phải fail ầm ĩ, chứ không im lặng tạo ra bảng mà chẳng ai nhận ra là sai.

Lựa chọn thứ ba hay bị bỏ quên nhất. Trong tự động hóa, "chuyển đổi thành công" và "chuyển đổi đúng" là hai mệnh đề khác nhau — và danh sách cột tường minh là cơ chế duy nhất tách chúng.

Khi khóa chứa dấu chấm, các đường dẫn làm phẳng va chạm nhau

Làm phẳng theo ký hiệu chấm biến {"a": {"b": 2}} thành cột a.b. Nhưng nếu khóa tự thân chứa dấu chấm? Đã thử với pandas:

import pandas as pd
d = [{"a.b": 1, "a": {"b": 2}}]
pd.json_normalize(d, sep=".")
# Chỉ một cột sống sót: ['a.b'] — hai khóa hoàn toàn khác nhau va chạm trên cùng đường dẫn

Khóa nguyên văn a.b và đường dẫn lồng nhau a → b sinh cùng một tên cột, cái này ghi đè cái kia, và dữ liệu hỏng trong im lặng. Ví dụ được dựng nên, nhưng với dữ liệu thật cũng xảy ra: tên chỉ số giám sát (cpu.usage) và trường log (error.type) mê dấu chấm.

Hai bài thuốc:

  1. Đổi ký tự phân tách. sep="_" hoặc ký tự tùy chọn — sau khi xác nhận khóa gốc không bao giờ chứa nó.
  2. Đổi ký hiệu đường dẫn. JSON Pointer dùng / làm phân tách (/a/b); bộ chuyển đổi Excel của toolvore đi con đường này. Nó cùng lớp mơ hồ — khóa chứa / phải ghi ~1 — nhưng quy tắc escape được chuẩn hóa trong RFC 6901 chứ không phải mỗi công cụ tự nghĩ ra.

Chọn gì đi nữa, hãy ghi quy tắc vào tài liệu xuất. Header CSV là "schema" duy nhất mà dữ liệu này mang theo; người đọc sẽ không tìm thấy quy tắc làm phẳng của bạn ở đâu khác.

Mảng: ba chiến lược, mỗi cái mất một thứ khác nhau

Mảng là chướng ngại thật sự của JSON sang CSV: một ô không thể chứa nhiều giá trị. Ba chiến lược, với tổn thất đã kiểm chứng trong thực tế:

Chiến lược Dạng đầu ra Mất gì Phù hợp cho
JSON trong ô Mảng được tuần tự hóa thành chuỗi JSON trong ô Công cụ bảng không thể lọc hay tổng hợp theo phần tử Trung thực trước, xem tay
Cột đánh chỉ số tags.0, tags.1, mỗi phần một cột Số cột bùng nổ theo mảng dài nhất; nhiều ô trống Mảng nhỏ độ dài cố định
Dòng bung Một dòng mỗi phần tử, trường cha lặp lại Phình dòng; hành vi mảng rỗng cần quy định riêng Phân tích chi tiết, tổng hợp theo phần tử

Các bộ chuyển đổi /convert/csv và /convert/table của chúng tôi dùng JSON trong ô. Đầu vào [{"orderId":"A001","items":[{"sku":"X1","qty":2},{"sku":"Y9","qty":1}]}] xuất ra:

orderId,items
A001,"[{""sku"":""X1"",""qty"":2},{""sku"":""Y9"",""qty"":1}]"

Cần dòng bung thì pandas record_path làm trực tiếp:

orders = [{"orderId": "A001", "total": 99,
           "items": [{"sku": "X1", "qty": 2}, {"sku": "Y9", "qty": 1}]}]
items = pd.json_normalize(orders, record_path=["items"],
                          meta=["orderId", "total"], sep=".")
# Hai dòng chi tiết; orderId và total lặp lại ở mỗi dòng

Khi bung có ba bẫy cần quyết trước: bung nhiều mảng cùng lúc dựng tích Descartes; mảng rỗng mặc định không sinh dòng nào (danh sách khách của bạn mất người); và trường cha lặp lại đòi khử trùng lặp khi tổng hợp theo orderId ở phía sau. Hướng dẫn convertcsvonline đặt "xác định mức chi tiết trước" là bước đầu chính vì thế — khi mức chi tiết chốt, cả ba câu hỏi mỗi câu một đáp án.

Đã kiểm chứng: giá trị ra khỏi chuyển đổi trong tình trạng đã thay đổi

Bài kiểm tra trung thực chạy trên bộ chuyển đổi thật của chúng tôi (Node v22.13.1 + papaparse, đúng bản triển khai của /convert/csv):

1. null, vắng mặt và chuỗi rỗng đều thành ô trống. Đầu vào [{"a":null},{"b":""},{"c":1}] — cả ba ô ra rỗng. CSV không có ba trạng thái, chỉ "có ký tự" và "không có ký tự". Sau một vòng đi-về, cả ba đọc là ""; ngữ nghĩa gốc không phục hồi được.

2. Đối tượng và mảng lồng nhau thành chuỗi JSON. Dấu ngoặc kép được nhân đôi theo RFC 4180 ({""name"":""张三""}), và dán ô vào bất kỳ parser JSON nào giá trị trở về — vì thế JSON trong ô giữ được độ trung thực.

3. Số nguyên 20 chữ số mất độ chính xác ngay ở bước phân tích — lỗi không nằm ở CSV. Đầu vào "id": 9007199254740993; trong CSV đã là 9007199254740992. JSON.parse của JavaScript dùng double IEEE 754: mất mát xảy ra lúc phân tích, trước mọi bộ chuyển đổi CSV. Python json.load giữ nguyên vẹn. Các trường kiểu định danh (snowflake ID, số đơn hàng) nên đến dạng chuỗi ngay từ bên sản xuất. Thêm về độ chính xác số học tại vì sao số lớn mất độ chính xác.

4. Số 0 đứng đầu trong chuỗi an toàn ở lớp văn bản của CSV. "phone": "00123" vẫn là 00123 trong CSV, vì nó là chuỗi từ đầu đến cuối và chẳng gì cào được nó. Cào số 0 là bước sau, khi Excel coi ô là số — xem bốn cách chuyển JSON sang Excel.

5. Trường chứa dấu phẩy, ngoặc kép hoặc xuống dòng. papaparse tuân RFC 4180: bao ô trong ngoặc, nhân đôi ngoặc trong. "He said \"hi\", ok" ra thành "He said ""hi"", ok" và qua vòng đi-về nguyên vẹn. Bộ tuần tự viết tay né thư viện hầu như luôn gãy đúng chỗ đó.

Vòng đi-về: công tắc suy luận thay đổi gì

CSV → JSON phải đoán kiểu cho từng ô. Bộ chuyển đổi của chúng tôi mặc định tắt suy luận kiểu và trả về toàn chuỗi:

[{ "id": "9007199254740992", "phone": "00123" }]

Bật typeInference thì id thành số (độ chính xác đã mất từ trước) còn phone vẫn chuỗi — quy tắc suy luận loại trừ dạng số 0 đứng đầu như 010, vốn không phải literal số hợp lệ, nên chuỗi sống sót nhờ may mắn.

Ghi nhớ bất đối xứng này: suy luận tắt tệ nhất lấy của bạn kiểu; suy luận bật có thể lấy của bạn dữ liệu. Cột cần số phải chuyển bằng định nghĩa cột tường minh, đừng phó thác cho suy luận toàn cục. Triển khai rowsToRecords trong bài trước JSON, CSV và dữ liệu bảng chính là nguyên tắc đó trong code.

Chọn chiến lược theo bên nhận: bảng quyết định

Ai nhận CSV Chiến lược header Xử lý mảng Kiểu dữ liệu
Đồng nghiệp mở một lần Hợp nhất khóa JSON trong ô Không suy luận
Excel / báo cáo Hợp nhất khóa JSON trong ô, hoặc cột đánh chỉ số nếu độ dài cố định Cột định danh ép thành văn bản
Nhập vào cơ sở dữ liệu Danh sách cột tường minh Bảng master/detail tách riêng, hoặc bung Định nghĩa cột tường minh
BI / tổng hợp Danh sách cột tường minh Dòng bung, mức chi tiết = đơn vị phân tích nhỏ nhất Định nghĩa cột tường minh
Pipeline tự động Danh sách cột tường minh JSON trong ô, bung ở phía sau Suy luận tắt, chuyển từng cột

Quy luật chung: càng gần con người, càng được phép nới lỏng; càng gần máy, càng phải tường minh mọi thứ.

Checklist để chép lại

  • [ ] Header đến từ hợp nhất khóa đầy đủ hay danh sách tường minh? Nếu không, bạn chứng minh được không một bản ghi nào mất trường?
  • [ ] Khóa có thể chứa dấu chấm hay gạch chéo? Đã kiểm tra va chạm với ký tự phân tách làm phẳng?
  • [ ] Chiến lược mảng đã được ghi chép? Hành vi mảng rỗng đã chốt?
  • [ ] Với null, vắng mặt và chuỗi rỗng: bên nhận đọc được gì từ mỗi cái? Chọn một quy tắc và đóng băng nó.
  • [ ] Cột kiểu định danh (ID, số đơn hàng, điện thoại) di chuyển dạng chuỗi từ đầu đến cuối?
  • [ ] Sau khi bung, số dòng bằng tổng độ dài các mảng? Đã kiểm tra ngẫu nhiên?
  • [ ] Trường mẫu chứa dấu phẩy, ngoặc kép và xuống dòng sống sót qua vòng đi-về nguyên vẹn?
  • [ ] Suy luận kiểu toàn cục tắt khi nhập lại, có định nghĩa tường minh cho cột số?

Chỉ khi cả tám có câu trả lời rõ ràng, tệp CSV này mới "đúng" — chứ không chỉ "chuyển đổi thành công".

FAQ

Vì sao bộ chuyển đổi không làm phẳng đối tượng lồng nhau thành cột theo mặc định?

Làm phẳng khiến số cột ngoài tầm kiểm soát: lồng sâu sinh tên kiểu user.address.geo.lat, và kết hợp với hợp nhất khóa, chỉ một bản ghi khác cấu trúc là thêm cả đống cột. Chuỗi JSON trong ô giữ độ trung thực và số cột ổn định; làm phẳng tường minh khi cần.

Header chứa tên cột trùng nhau thì làm gì?

Va chạm hai khóa khác nhau sau khi làm phẳng (như a.b đối chọi với a→b lồng nhau) phải giải ở phía trước: đổi ký tự phân tách hoặc chuyển sang JSON Pointer. Với trùng lặp trong cùng một đối tượng, bộ chuyển đổi thường giữ giá trị sau cùng; trùng lặp trong header tùy triển khai — unparse của papaparse không khử trùng lặp, Excel khi mở sẽ âm thầm gộp hoặc bỏ trùng lặp. Vì thế nó nằm trong checklist.

Trường cha lặp lại sau khi bung có phải lỗi không?

Không — đó là thiết kế. Dòng bung là dòng chi tiết, và dòng chi tiết phải nối lại được với bản ghi cha theo khóa. Tổng hợp phía sau theo khóa cha; nếu kích thước đáng lo, xuất tệp chi tiết và một bảng master riêng.

Làm sao kiểm chứng chuyển đổi của tôi không mất trường nào?

Làm kiểm toán khóa: tính hợp nhất mọi khóa của JSON nguồn, rồi tập hợp header cột của CSV, lấy hiệu. Hiệu khác rỗng nghĩa là mất trường. Trong pipeline theo lô, mã hóa điều này thành assert trong kiểm thử: bất kỳ thay đổi bộ chuyển đổi nào bắt đầu mất trường sẽ tự động fail.