Python chuyển JSON sang CSV — Ví dụ DictWriter + pandas
Sử dụng Chuyển JSON sang CSV miễn phí trực tiếp trên trình duyệt — không cần cài đặt.
Dùng thử Chuyển JSON sang CSV trực tuyến →Hầu như mọi pipeline dữ liệu đều chạm đến cùng một bước: API trả về JSON, nhưng người dùng tiếp theo — bảng tính, script nhập liệu, lệnh Redshift COPY — lại cần CSV. Việc chuyển đổi JSON sang CSV trong Python nghe có vẻ đơn giản cho đến khi bạn gặp các đối tượng lồng nhau, khóa không nhất quán, hoặc giá trị datetime cần xử lý đặc biệt. Python cung cấp hai con đường vững chắc: các module tích hợp json + csv cho các script không có phụ thuộc, và pandas để làm phẳng dữ liệu lồng nhau và xử lý dataset lớn hơn — hoặc công cụ chuyển đổi JSON sang CSV trực tuyến cho các chuyển đổi nhanh một lần mà không cần code. Hướng dẫn này bao gồm cả hai cách tiếp cận từ đầu đến cuối, với các ví dụ có thể chạy được bằng Python 3.8+.
- ✓csv.DictWriter chuyển đổi danh sách các dict sang CSV không cần phụ thuộc — dùng json.load() để phân tích, sau đó writeheader() + writerows().
- ✓Luôn mở file CSV với newline="" trên Windows để tránh các hàng trống giữa các hàng dữ liệu.
- ✓pd.json_normalize() làm phẳng JSON lồng nhau thành DataFrame phẳng trước khi gọi to_csv() — tự động xử lý lồng nhiều cấp.
- ✓Truyền index=False vào DataFrame.to_csv() — không có nó, pandas ghi thêm cột số thứ tự hàng không mong muốn.
- ✓Với file trên 500 MB, dùng ijson để stream phân tích JSON kết hợp với csv.DictWriter để sử dụng bộ nhớ ổn định.
Chuyển đổi JSON sang CSV là gì?
Chuyển đổi JSON sang CSV biến đổi một mảng các đối tượng JSON thành định dạng dạng bảng trong đó mỗi đối tượng trở thành một hàng và mỗi khóa trở thành tiêu đề cột. JSON có cấu trúc phân cấp — các đối tượng có thể lồng sâu tùy ý. CSV là phẳng — mọi giá trị nằm trong lưới hàng-cột. Việc chuyển đổi hoạt động tốt khi mọi đối tượng có cùng tập hợp khóa cấp cao nhất. Các đối tượng lồng nhau, mảng, và khóa không nhất quán là nơi mọi thứ trở nên phức tạp. Dữ liệu thô không thay đổi; chỉ cấu trúc thay đổi.
[{"order_id":"ord_91a3","total":149.99,"status":"shipped"},
{"order_id":"ord_b7f2","total":34.50,"status":"pending"}]order_id,total,status ord_91a3,149.99,shipped ord_b7f2,34.50,pending
csv.DictWriter — Chuyển đổi JSON sang CSV không dùng Pandas
Module csv được cài sẵn trong mọi bản Python. Không cần pip install, không cần vật lộn với môi trường ảo. csv.DictWriter nhận danh sách các dictionary và ghi mỗi cái thành một hàng CSV, ánh xạ khóa dict vào tiêu đề cột. Tham số fieldnames kiểm soát cả thứ tự cột lẫn các khóa nào được đưa vào.
import json
import csv
# Dữ liệu JSON mẫu — một mảng các đối tượng đơn hàng
json_string = """
[
{"order_id": "ord_91a3", "product": "Wireless Keyboard", "quantity": 2, "unit_price": 74.99},
{"order_id": "ord_b7f2", "product": "USB-C Hub", "quantity": 1, "unit_price": 34.50},
{"order_id": "ord_c4e8", "product": "Monitor Stand", "quantity": 3, "unit_price": 29.95}
]
"""
records = json.loads(json_string)
with open("orders.csv", "w", newline="", encoding="utf-8") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
# orders.csv:
# order_id,product,quantity,unit_price
# ord_91a3,Wireless Keyboard,2,74.99
# ord_b7f2,USB-C Hub,1,34.50
# ord_c4e8,Monitor Stand,3,29.95Tham số newline="" trên open() là bắt buộc trên Windows. Nếu không có nó, bạn sẽ nhận được double carriage return — hiển thị như các hàng trống giữa mỗi hàng dữ liệu trong Excel. Trên macOS và Linux nó vô hại, vì vậy hãy luôn thêm vào.
Đoạn code trên dùng json.loads() cho chuỗi. Dùng json.load() (không có s ở cuối) khi đọc từ file handle. Điều này hay gây nhầm lẫn — một cái đọc chuỗi, cái kia đọc đối tượng file.
import json
import csv
with open("server_metrics.json", encoding="utf-8") as jf:
metrics = json.load(jf) # json.load() cho đối tượng file
# Fieldnames tường minh kiểm soát thứ tự cột
columns = ["timestamp", "hostname", "cpu_percent", "memory_mb", "disk_io_ops"]
with open("server_metrics.csv", "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=columns, extrasaction="ignore")
writer.writeheader()
writer.writerows(metrics)
# Chỉ năm cột được chỉ định xuất hiện, theo đúng thứ tự đóĐặt extrasaction="ignore" sẽ bỏ qua các khóa trong dict không có trong danh sách fieldnames. Mặc định là "raise", sẽ ném ValueError nếu có khóa không mong đợi trong dict. Chọn cái phù hợp với mức độ chịu đựng bất ngờ của bạn.
csv.DictWriter vs csv.writer: DictWriter tự động ánh xạ khóa dict vào vị trí cột. csv.writer ghi danh sách thô thành hàng — bạn tự xử lý thứ tự cột. DictWriter hầu như luôn là lựa chọn đúng cho JSON-to-CSV vì các bản ghi JSON đã là dictionary.Module csv của Python đi kèm ba dialect có tên: excel (dấu phẩy, ký tự xuống dòng CRLF — mặc định), excel-tab (tab, CRLF), và unix (LF, bao ngoặc tất cả trường không phải số). Truyền tên dialect vào tham số dialect của csv.DictWriter. Bạn cũng có thể định nghĩa dialect tùy chỉnh bằng csv.register_dialect() khi hệ thống đích có quy tắc bao ngoặc hoặc dấu phân cách bất thường. Với hầu hết workflow JSON-to-CSV, dialect excel là đúng, nhưng chuyển sang unix khi ghi file sẽ được xử lý bởi các công cụ POSIX như awk hoặc sort.
Xử lý kiểu không chuẩn: datetime, UUID và Decimal
JSON từ API thường chứa ngày dưới dạng chuỗi ISO, UUID dưới dạng chuỗi có dấu gạch ngang, và giá trị tiền tệ dưới dạng float. Khi bạn phân tích chúng thành đối tượng Python để xử lý trước khi ghi CSV, bạn cần chuyển đổi chúng lại thành chuỗi. Module csv gọi str() trên mỗi giá trị, nên hầu hết kiểu đều hoạt động. Nhưng đối tượng datetime tạo ra chuỗi mặc định lộn xộn, và giá trị Decimal cần định dạng tường minh để tránh ký hiệu khoa học.
import json
import csv
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID
# Mô phỏng phản hồi API đã được phân tích với kiểu Python
transactions = [
{
"txn_id": UUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
"created_at": datetime(2026, 3, 15, 9, 30, 0, tzinfo=timezone.utc),
"amount": Decimal("1249.99"),
"currency": "USD",
"merchant": "CloudHost Inc.",
},
{
"txn_id": UUID("b2c3d4e5-f6a7-8901-bcde-f12345678901"),
"created_at": datetime(2026, 3, 15, 14, 12, 0, tzinfo=timezone.utc),
"amount": Decimal("87.50"),
"currency": "EUR",
"merchant": "DataSync GmbH",
},
]
def prepare_row(record: dict) -> dict:
"""Chuyển đổi kiểu không phải chuỗi sang chuỗi thân thiện với CSV."""
return {
"txn_id": str(record["txn_id"]),
"created_at": record["created_at"].isoformat(),
"amount": f"{record['amount']:.2f}",
"currency": record["currency"],
"merchant": record["merchant"],
}
with open("transactions.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["txn_id", "created_at", "amount", "currency", "merchant"])
writer.writeheader()
for txn in transactions:
writer.writerow(prepare_row(txn))
# transactions.csv:
# txn_id,created_at,amount,currency,merchant
# a1b2c3d4-e5f6-7890-abcd-ef1234567890,2026-03-15T09:30:00+00:00,1249.99,USD,CloudHost Inc.
# b2c3d4e5-f6a7-8901-bcde-f12345678901,2026-03-15T14:12:00+00:00,87.50,EUR,DataSync GmbHHàm prepare_row() là cách tiếp cận đúng ở đây. Thay vì cố dạy csv.DictWriter về các kiểu tùy chỉnh, bạn chuẩn hóa mỗi bản ghi về chuỗi trước khi ghi. Tôi thích gọi .isoformat() tường minh trên đối tượng datetime hơn là dùng str() — định dạng đầu ra có thể dự đoán hơn, và các parser downstream xử lý ISO 8601 đáng tin cậy.
Decimal đi qua mà không định dạng, các số rất nhỏ hoặc rất lớn có thể hiển thị dưới ký hiệu khoa học (ví dụ: 1.5E+7). Luôn định dạng Decimal bằng f-string tường minh như f"{value:.2f}" khi ghi dữ liệu tài chính vào CSV.Một mẫu thay thế cho pipeline có nhiều kiểu tùy chỉnh là kế thừa json.JSONEncoder. Tạo lớp con, ghi đè phương thức default() để trả về giá trị JSON-serializable cho mỗi kiểu tùy chỉnh, rồi truyền lớp con làm tham số cls cho json.dumps(). Mã hóa lại qua encoder tùy chỉnh trước khi ghi CSV chuẩn hóa tất cả các kiểu trong một bước mà không cần gọi prepare_row() cho từng hàng. Mẫu prepare_row() ở trên đơn giản hơn cho script dùng một lần; cách tiếp cận JSONEncoder subclass mở rộng tốt hơn khi cùng domain model với kiểu tùy chỉnh được chia sẻ trên nhiều giai đoạn pipeline hoặc microservice.
Tài liệu tham số csv.DictWriter
Chữ ký constructor đầy đủ là csv.DictWriter(f, fieldnames, restval="", extrasaction="raise", dialect="excel", **fmtparams). Hầu hết có giá trị mặc định hợp lý. Những tham số bạn thực sự sẽ thay đổi là fieldnames, delimiter, và extrasaction.
pandas — Chuyển đổi JSON sang CSV với DataFrames
Nếu bạn đang làm việc trong codebase sử dụng nhiều pandas, hoặc JSON của bạn có đối tượng lồng nhau cần làm phẳng, cách tiếp cận pandas ít code hơn đáng kể so với phiên bản stdlib. Sự đánh đổi: pandas là phụ thuộc ~30 MB. Với script tạm thời, điều đó ổn. Với Docker image bạn triển khai lên production, cách stdlib giữ mọi thứ gọn nhẹ hơn.
import pandas as pd
# Đọc mảng JSON trực tiếp vào DataFrame
df = pd.read_json("warehouse_inventory.json")
# Ghi ra CSV — index=False ngăn số thứ tự hàng tự tạo
df.to_csv("warehouse_inventory.csv", index=False)
# Chỉ vậy thôi. Hai dòng. pandas tự suy luận kiểu cột.Tham số index=False là thứ bạn phải tra cứu mỗi lần. Nếu không có nó, pandas ghi cột 0, 1, 2, ... làm cột đầu tiên của CSV. Không ai muốn điều đó.
Làm phẳng JSON lồng nhau với json_normalize
Phản hồi API thực tế hiếm khi phẳng. Đơn hàng chứa địa chỉ giao hàng, người dùng chứa tuỳ chọn lồng nhau, sự kiện đo từ xa chứa metadata lồng nhau. pd.json_normalize() duyệt các dictionary lồng nhau và làm phẳng chúng thành các cột với tên phân cách bằng dấu chấm.
import json
import pandas as pd
api_response = """
[
{
"order_id": "ord_91a3",
"placed_at": "2026-03-15T09:30:00Z",
"customer": {
"name": "Sarah Chen",
"email": "s.chen@example.com",
"tier": "premium"
},
"shipping": {
"method": "express",
"address": {
"city": "Portland",
"state": "OR",
"zip": "97201"
}
},
"total": 299.95
},
{
"order_id": "ord_b7f2",
"placed_at": "2026-03-15T14:12:00Z",
"customer": {
"name": "James Park",
"email": "j.park@example.com",
"tier": "standard"
},
"shipping": {
"method": "standard",
"address": {
"city": "Austin",
"state": "TX",
"zip": "73301"
}
},
"total": 87.50
}
]
"""
orders = json.loads(api_response)
# json_normalize làm phẳng các dict lồng nhau — sep kiểm soát dấu phân cách
df = pd.json_normalize(orders, sep="_")
df.to_csv("flat_orders.csv", index=False)
# Các cột kết quả:
# order_id, placed_at, customer_name, customer_email, customer_tier,
# shipping_method, shipping_address_city, shipping_address_state,
# shipping_address_zip, totalTham số sep="_" kiểm soát cách các tên khóa lồng nhau được nối. Mặc định là ".", tạo ra các cột như customer.name. Tôi thích dấu gạch dưới vì dấu chấm trong tên cột gây rắc rối với import SQL và một số công thức bảng tính.
Với các phản hồi API bọc mảng bản ghi dưới một khóa lồng nhau, dùng tham số record_path. Nếu phản hồi trông như {"data": {"orders": [...]}}, truyền record_path=["data", "orders"] để điều hướng đến danh sách đúng. Tham số tùy chọn meta cho phép bạn kéo các trường cấp cha cùng với các bản ghi lồng nhau — hữu ích khi phản hồi có thông tin phân trang cấp cao nhất (số trang, tổng số) mà bạn muốn làm cột trong mỗi hàng. Cùng nhau, record_path và meta xử lý hầu hết các hình dạng phản hồi API lồng nhau thực tế mà không cần tiền xử lý tùy chỉnh.
Tài liệu tham số DataFrame.to_csv()
DataFrame.to_csv() có hơn 20 tham số. Đây là những tham số quan trọng cho workflow JSON-to-CSV.
import pandas as pd
df = pd.read_json("telemetry_events.json")
# Xuất TSV với encoding tường minh và xử lý giá trị thiếu
df.to_csv(
"telemetry_events.tsv",
sep="\t",
index=False,
encoding="utf-8",
na_rep="NULL",
columns=["event_id", "timestamp", "source", "severity", "message"],
)
# Ghi ra stdout để pipe trong shell script
print(df.to_csv(index=False))
# Trả về dưới dạng chuỗi (không ghi file)
csv_string = df.to_csv(index=False)
print(len(csv_string), "ký tự")Chuyển đổi JSON sang CSV từ File và Phản hồi API
Hai tình huống thực tế phổ biến nhất: đọc JSON từ file trên đĩa và chuyển đổi, hoặc lấy JSON từ HTTP API và lưu kết quả dưới dạng CSV. Trong quá trình phát triển, bạn có thể bỏ qua xử lý lỗi. Trong production, lựa chọn đó trở thành cảnh báo lúc 2 giờ sáng. File có thể không tồn tại, API có thể trả về mã trạng thái 4xx hoặc 5xx thay vì JSON, body phản hồi có thể là đối tượng lỗi thay vì mảng, hoặc JSON có thể bị cắt ngắn do timeout mạng. Các mẫu dưới đây xử lý tất cả các trường hợp này một cách tường minh, ghi lỗi ra stderr, và trả về số hàng để người gọi có thể phát hiện đầu ra rỗng và cảnh báo tương ứng.
File trên đĩa — Đọc, Chuyển đổi, Lưu
import json
import csv
import sys
def json_file_to_csv(input_path: str, output_path: str) -> int:
"""Chuyển đổi file JSON chứa mảng đối tượng sang CSV.
Trả về số hàng đã ghi.
"""
try:
with open(input_path, encoding="utf-8") as jf:
data = json.load(jf)
except FileNotFoundError:
print(f"Error: {input_path} not found", file=sys.stderr)
return 0
except json.JSONDecodeError as exc:
print(f"Error: invalid JSON in {input_path}: {exc.msg} at line {exc.lineno}", file=sys.stderr)
return 0
if not isinstance(data, list) or not data:
print(f"Error: expected a non-empty JSON array in {input_path}", file=sys.stderr)
return 0
# Thu thập tất cả các khóa duy nhất từ tất cả bản ghi — xử lý schema không nhất quán
all_keys: list[str] = []
seen: set[str] = set()
for record in data:
for key in record:
if key not in seen:
all_keys.append(key)
seen.add(key)
with open(output_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=all_keys, restval="", extrasaction="ignore")
writer.writeheader()
writer.writerows(data)
return len(data)
rows = json_file_to_csv("deploy_logs.json", "deploy_logs.csv")
print(f"Wrote {rows} rows to deploy_logs.csv")Phản hồi HTTP API — Lấy và Chuyển đổi
import json
import csv
import urllib.request
import urllib.error
def api_response_to_csv(url: str, output_path: str) -> int:
"""Lấy JSON từ endpoint REST API và ghi dưới dạng CSV."""
try:
req = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=30) as resp:
if resp.status != 200:
print(f"Error: API returned status {resp.status}")
return 0
body = resp.read().decode("utf-8")
except urllib.error.URLError as exc:
print(f"Error: could not reach {url}: {exc.reason}")
return 0
try:
records = json.loads(body)
except json.JSONDecodeError as exc:
print(f"Error: API returned invalid JSON: {exc.msg}")
return 0
if not isinstance(records, list) or not records:
print("Error: expected a non-empty JSON array from the API")
return 0
with open(output_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)
return len(records)
rows = api_response_to_csv(
"https://api.internal.example.com/v2/deployments?status=completed",
"completed_deployments.csv",
)
print(f"Exported {rows} deployments to CSV")urllib từ thư viện chuẩn để giữ script không có phụ thuộc. Nếu bạn đã cài requests, thay thế phần urllib bằng resp = requests.get(url, timeout=30); records = resp.json() — phần code ghi CSV còn lại giữ nguyên.Chuyển đổi JSON sang CSV qua Dòng lệnh
Đôi khi bạn chỉ cần một dòng lệnh trong terminal. Flag -c của Python cho phép chạy chuyển đổi nhanh mà không cần tạo file script. Với các phép biến đổi phức tạp hơn, pipe qua jq trước để định hình lại dữ liệu, sau đó chuyển đổi.
# Một dòng lệnh Python: đọc JSON từ stdin, ghi CSV ra stdout cat orders.json | python3 -c " import json, csv, sys data = json.load(sys.stdin) w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys()) w.writeheader() w.writerows(data) " # Lưu đầu ra vào file cat orders.json | python3 -c " import json, csv, sys data = json.load(sys.stdin) w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys()) w.writeheader() w.writerows(data) " > orders.csv
# Lưu thành json2csv.py và chạy: python3 json2csv.py input.json -o output.csv
python3 -c "
import json, csv, argparse, sys
parser = argparse.ArgumentParser(description='Convert JSON array to CSV')
parser.add_argument('input', help='Path to JSON file')
parser.add_argument('-o', '--output', default=None, help='Output CSV path (default: stdout)')
parser.add_argument('-d', '--delimiter', default=',', help='CSV delimiter')
args = parser.parse_args()
with open(args.input) as f:
data = json.load(f)
out = open(args.output, 'w', newline='') if args.output else sys.stdout
writer = csv.DictWriter(out, fieldnames=data[0].keys(), delimiter=args.delimiter)
writer.writeheader()
writer.writerows(data)
if args.output:
out.close()
print(f'Wrote {len(data)} rows to {args.output}', file=sys.stderr)
" "$@"# Cài csvkit: pip install csvkit
# jq làm phẳng và chọn trường, in2csv xử lý định dạng CSV
cat api_response.json | jq '[.[] | {id: .order_id, customer: .customer.name, total}]' | in2csv -f json > orders.csv
# Miller (mlr) là một lựa chọn khác cho JSON-to-CSV
mlr --json2csv cat orders.json > orders.csvMiller (mlr) là binary độc lập coi JSON, CSV và TSV là các định dạng hạng nhất không cần Python runtime. Flag --json2csv chuyển đổi JSON đầu vào sang CSV trong một lượt, và bạn có thể ghép chuỗi các lệnh Miller để lọc, sắp xếp hoặc đổi tên cột trong cùng lệnh trước khi ghi đầu ra. Cài qua Homebrew trên macOS (brew install miller) hoặc trình quản lý gói Linux. Đặc biệt hữu ích trong pipeline CI khi bạn muốn chuyển đổi JSON-to-CSV nhanh mà không cần khởi động môi trường Python.
Giải pháp hiệu suất cao — pandas với pyarrow
Với dataset hàng chục triệu hàng, pandas với backend pyarrow đọc và ghi nhanh hơn đáng kể so với mặc định. Engine Arrow được hỗ trợ C xử lý dữ liệu dạng cột hiệu quả hơn module csv từng hàng của Python. API giữ nguyên — bạn chỉ đặt tham số engine.
pip install pyarrow
import pandas as pd
# Đọc JSON với engine pyarrow (phân tích nhanh hơn cho file lớn)
df = pd.read_json("sensor_readings.json", engine="pyarrow")
# to_csv không có tham số engine, nhưng các phép tính DataFrame
# giữa đọc và ghi được hưởng lợi từ bố cục cột của pyarrow
df.to_csv("sensor_readings.csv", index=False)
# Với export thực sự lớn, cân nhắc ghi ra Parquet thay vì CSV
# — định dạng nhị phân, nhỏ hơn 5-10x, bảo toàn kiểu
df.to_parquet("sensor_readings.parquet", engine="pyarrow")Nếu bạn đang xử lý hơn vài trăm MB JSON và người tiêu thụ cuối chấp nhận Parquet, hãy bỏ qua CSV hoàn toàn. Parquet nhỏ hơn, bảo toàn kiểu cột, và cả Redshift lẫn BigQuery đều tải nó trực tiếp. CSV là định dạng mất thông tin — mọi giá trị trở thành chuỗi.
Đầu ra Terminal với Syntax Highlighting
Thư viện rich hiển thị bảng với viền, căn chỉnh và màu sắc trong terminal — hữu ích để xem trước chuyển đổi trong quá trình phát triển mà không cần mở file đầu ra.
pip install rich
import json
from rich.console import Console
from rich.table import Table
json_string = """
[
{"hostname": "web-prod-1", "cpu_percent": 72.3, "memory_mb": 3840, "uptime_hours": 720},
{"hostname": "web-prod-2", "cpu_percent": 45.1, "memory_mb": 2560, "uptime_hours": 168},
{"hostname": "db-replica-1", "cpu_percent": 91.7, "memory_mb": 7680, "uptime_hours": 2160}
]
"""
records = json.loads(json_string)
console = Console()
table = Table(title="Server Metrics Preview", show_lines=True)
for key in records[0]:
table.add_column(key, style="cyan" if key == "hostname" else "white")
for row in records:
table.add_row(*[str(v) for v in row.values()])
console.print(table)
# Hiển thị bảng có màu với viền trong terminalcsv.DictWriter hoặc DataFrame.to_csv(), và chỉ dùng rich để xem trước.Làm việc với File JSON lớn
json.load() đọc toàn bộ file vào bộ nhớ. Với file JSON 200 MB, điều đó có nghĩa là ~200 MB văn bản thô cộng với overhead đối tượng Python — dễ dàng hơn 500 MB+ heap. Với file trên 100 MB, hãy stream đầu vào bằng ijson và ghi từng hàng CSV khi tiến hành.
pip install ijson
Stream mảng JSON sang CSV với ijson
import ijson
import csv
def stream_json_to_csv(json_path: str, csv_path: str) -> int:
"""Chuyển đổi mảng JSON lớn sang CSV mà không tải tất cả vào bộ nhớ."""
with open(json_path, "rb") as jf, open(csv_path, "w", newline="", encoding="utf-8") as cf:
# ijson.items yield từng phần tử của mảng cấp cao nhất một lần một
records = ijson.items(jf, "item")
first_record = next(records)
fieldnames = list(first_record.keys())
writer = csv.DictWriter(cf, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(first_record)
count = 1
for record in records:
writer.writerow(record)
count += 1
return count
rows = stream_json_to_csv("clickstream_2026_03.json", "clickstream_2026_03.csv")
print(f"Streamed {rows} records to CSV")NDJSON / JSON Lines — Một đối tượng mỗi dòng
NDJSON (Newline-Delimited JSON), còn gọi là JSON Lines hoặc .jsonl, lưu trữ một đối tượng JSON hợp lệ mỗi dòng mà không có mảng bao bọc. Định dạng này phổ biến trong pipeline log, luồng sự kiện (Kafka, Kinesis), và xuất hàng loạt từ các dịch vụ như Elasticsearch và BigQuery. Vì mỗi dòng là một đối tượng JSON độc lập, bạn có thể xử lý file NDJSON bằng vòng lặp for Python đơn giản qua file handle — không cần thư viện ijson. Bộ nhớ giữ ổn định bất kể kích thước file, làm cho đây là cách tiếp cận streaming đơn giản nhất khi dữ liệu nguồn đã ở định dạng JSON Lines.
import json
import csv
def ndjson_to_csv(ndjson_path: str, csv_path: str) -> int:
"""Chuyển đổi file JSON phân cách dòng mới sang CSV, từng dòng một."""
with open(ndjson_path, encoding="utf-8") as nf:
first_line = nf.readline()
first_record = json.loads(first_line)
fieldnames = list(first_record.keys())
with open(csv_path, "w", newline="", encoding="utf-8") as cf:
writer = csv.DictWriter(cf, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(first_record)
count = 1
for line in nf:
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
writer.writerow(record)
count += 1
except json.JSONDecodeError:
continue # bỏ qua các dòng lỗi
return count
rows = ndjson_to_csv("access_log.ndjson", "access_log.csv")
print(f"Converted {rows} log entries to CSV")json.load() có thể tiêu tốn 3–5 GB RAM do overhead đối tượng Python. Với ijson, bộ nhớ giữ ổn định bất kể kích thước file. Nếu bạn chỉ cần chuyển đổi nhanh một file nhỏ, hãy dán vào công cụ chuyển đổi JSON sang CSV thay thế.Lỗi thường gặp
Vấn đề: Module csv ghi ký tự xuống dòng \r\n. Nếu không có newline='', text mode của Python thêm thêm một \r trên Windows, tạo ra đầu ra double-spaced.
Giải pháp: Luôn truyền newline='' khi mở file để ghi CSV. Vô hại trên macOS/Linux.
with open("output.csv", "w") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Hàng trống giữa mỗi hàng dữ liệu trên Windowswith open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(data)
# Đầu ra sạch trên tất cả nền tảngVấn đề: Nếu không có index=False, pandas thêm cột số thứ tự hàng tự tăng (0, 1, 2, ...) làm ô nhiễm CSV với dữ liệu không bao giờ có trong JSON gốc.
Giải pháp: Truyền index=False vào to_csv(). Nếu bạn thực sự cần cột chỉ mục, đặt tên tường minh bằng df.index.name = 'row_num'.
df = pd.read_json("events.json")
df.to_csv("events.csv")
# CSV có thêm cột không tên: ,event_id,timestamp,...
# Dấu phẩy dẫn đầu phá vỡ nhiều CSV parserdf = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# CSV sạch: event_id,timestamp,...Vấn đề: Nếu các đối tượng JSON có khóa khác nhau (một số bản ghi có trường tùy chọn), việc dùng khóa của bản ghi đầu tiên làm fieldnames sẽ bỏ qua các cột chỉ xuất hiện trong các bản ghi sau.
Giải pháp: Thu thập tất cả các khóa duy nhất từ tất cả bản ghi trước khi tạo DictWriter.
records = json.load(f) writer = csv.DictWriter(out, fieldnames=records[0].keys()) # Bỏ lỡ trường "discount" chỉ xuất hiện trong records[2]
records = json.load(f) all_keys = list(dict.fromkeys(k for r in records for k in r)) writer = csv.DictWriter(out, fieldnames=all_keys, restval="") # Mọi khóa từ mọi bản ghi đều được đưa vào như một cột
Vấn đề: csv.DictWriter gọi str() trên dict lồng nhau, tạo ra các cột có giá trị như "{'city': 'Portland'}"— Python repr thô, không phải dữ liệu thực.
Giải pháp: Làm phẳng các đối tượng lồng nhau trước bằng pd.json_normalize() hoặc hàm làm phẳng tùy chỉnh.
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
writer = csv.DictWriter(f, fieldnames=["id", "meta"])
writer.writerows(records)
# cột meta chứa: {'source': 'web', 'region': 'us-west'}import pandas as pd
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
df = pd.json_normalize(records, sep="_")
df.to_csv("events.csv", index=False)
# Các cột: id, meta_source, meta_regioncsv.DictWriter vs pandas — So sánh nhanh
Dùng csv.DictWriter khi bạn cần không có phụ thuộc, JSON của bạn phẳng, và script chạy trong môi trường hạn chế (CI container, Lambda function, Python nhúng). Dùng pd.json_normalize() + to_csv() khi JSON lồng nhau, bạn cần biến đổi hoặc lọc dữ liệu trước khi xuất, hoặc bạn đã trong workflow pandas. Với file không vừa với bộ nhớ, kết hợp ijson với csv.DictWriter để streaming bộ nhớ ổn định.
Để chuyển đổi nhanh không cần code, công cụ JSON sang CSV trên ToolDeck xử lý điều đó mà không cần cài đặt Python.
Câu hỏi thường gặp
Làm thế nào để chuyển đổi JSON sang CSV trong Python mà không dùng pandas?
Sử dụng các module json và csv tích hợp sẵn. Gọi json.load() để phân tích file JSON thành danh sách các dict, lấy fieldnames từ các khóa của dict đầu tiên, tạo csv.DictWriter, gọi writeheader(), rồi writerows(). Cách này không có phụ thuộc bên ngoài và hoạt động trong mọi môi trường Python 3.x. Nó cũng chạy nhanh hơn pandas với các file nhỏ vì không có chi phí khởi tạo DataFrame. Nếu các đối tượng JSON có khóa không nhất quán giữa các bản ghi, hãy thu thập tất cả các khóa duy nhất trước bằng dict.fromkeys(k for r in records for k in r) trước khi truyền làm fieldnames để tránh mất cột.
import json
import csv
with open("orders.json") as f:
records = json.load(f)
with open("orders.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=records[0].keys())
writer.writeheader()
writer.writerows(records)Làm thế nào để xử lý JSON lồng nhau khi chuyển đổi sang CSV?
Mảng JSON phẳng ánh xạ trực tiếp thành các hàng CSV, nhưng các đối tượng lồng nhau cần được làm phẳng trước. Với pandas, pd.json_normalize() xử lý điều này tự động — nó nối các khóa lồng nhau bằng dấu chấm (ví dụ: "address.city"). Không dùng pandas, hãy viết hàm đệ quy duyệt dict và nối các khóa bằng dấu phân cách. Với cấu trúc lồng sâu nhiều cấp, json_normalize xử lý tất cả trong một lần. Tham số sep kiểm soát ký tự nối giữa các đoạn khóa — dấu gạch dưới thường an toàn hơn dấu chấm mặc định cho việc nhập SQL và tương thích công thức bảng tính.
import pandas as pd
nested_data = [
{"id": "ord_91a3", "customer": {"name": "Nguyễn Thị Lan", "email": "nguyen.lan@example.com"}},
]
df = pd.json_normalize(nested_data, sep="_")
# Các cột: id, customer_name, customer_email
df.to_csv("flat_orders.csv", index=False)Tại sao CSV của tôi có các hàng trống giữa các hàng dữ liệu trên Windows?
Module csv mặc định ghi ký tự xuống dòng \r\n. Trên Windows, mở file ở chế độ text mode thêm một \r nữa, tạo ra \r\r\n — hiển thị như một hàng trống. Cách khắc phục là luôn truyền newline="" cho open(). Điều này báo Python không dịch ký tự xuống dòng, để module csv tự xử lý. Mẫu này bắt buộc bất kể hệ điều hành — vô hại trên macOS và Linux, quan trọng trên Windows. Tài liệu Python đề cập rõ ràng điều này trong phần module csv là cách đúng để mở file khi ghi CSV.
# Sai — hàng trống trên Windows
with open("output.csv", "w") as f:
writer = csv.writer(f)
# Đúng — newline="" ngăn \r kép
with open("output.csv", "w", newline="") as f:
writer = csv.writer(f)Làm thế nào để nối thêm bản ghi JSON vào file CSV đã có?
Mở file ở chế độ nối thêm ("a") và tạo DictWriter với cùng fieldnames. Bỏ qua writeheader() vì hàng tiêu đề đã tồn tại. Với pandas, dùng to_csv(mode="a", header=False). Đảm bảo thứ tự cột khớp với file hiện có, nếu không dữ liệu sẽ rơi vào cột sai. Nếu không chắc về thứ tự cột trong file hiện có, hãy mở nó trước bằng csv.DictReader và đọc fieldnames từ thuộc tính fieldnames trước khi tạo writer để nối thêm.
import csv
new_records = [
{"order_id": "ord_f4c1", "total": 89.50, "status": "shipped"},
]
with open("orders.csv", "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["order_id", "total", "status"])
writer.writerows(new_records)Cách nhanh nhất để chuyển đổi file JSON lớn sang CSV trong Python là gì?
Với file dưới 500 MB, pd.read_json() theo sau bởi to_csv() là cách nhanh nhất một lần gọi — pandas dùng mã C được tối ưu hóa nội bộ. Với file trên 500 MB, dùng ijson để stream các bản ghi JSON và ghi chúng vào CSV bằng csv.DictWriter từng hàng. Điều này giữ mức sử dụng bộ nhớ ổn định bất kể kích thước file. Với file NDJSON (một đối tượng JSON mỗi dòng), bạn không cần ijson — một vòng lặp Python đơn giản qua file handle xử lý từng dòng độc lập và đạt được bộ nhớ ổn định mà không cần thư viện bên thứ ba.
# Nhanh cho file vừa với bộ nhớ
import pandas as pd
df = pd.read_json("large_dataset.json")
df.to_csv("large_dataset.csv", index=False)
# Streaming cho file không vừa với bộ nhớ
import ijson, csv
with open("huge.json", "rb") as jf, open("huge.csv", "w", newline="") as cf:
records = ijson.items(jf, "item")
first = next(records)
writer = csv.DictWriter(cf, fieldnames=first.keys())
writer.writeheader()
writer.writerow(first)
for record in records:
writer.writerow(record)Tôi có thể ghi đầu ra CSV ra stdout thay vì file trong Python không?
Có. Truyền sys.stdout làm đối tượng file cho csv.writer() hoặc csv.DictWriter(). Điều này hữu ích khi pipe đầu ra trong shell script hoặc debug nhanh. Với pandas, gọi to_csv(sys.stdout, index=False) hoặc to_csv(None) để lấy chuỗi bạn có thể in ra. Không cần file tạm. Khi ghi ra stdout trên Windows, gọi sys.stdout.reconfigure(newline="") trước để tránh lỗi double carriage-return, vì stdout mặc định mở ở text mode.
import csv
import sys
import json
data = json.loads('[{"host":"web-1","cpu":72.3},{"host":"web-2","cpu":45.1}]')
writer = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
# host,cpu
# web-1,72.3
# web-2,45.1Công cụ liên quan
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.
Priya is a data scientist and machine learning engineer who has worked across the full Python data stack — from raw data ingestion and cleaning to model deployment and monitoring. She is passionate about reproducible research, Jupyter-based workflows, and the practical engineering side of ML. She writes about NumPy, Pandas, data serialisation, and the Python patterns that make data pipelines reliable at scale.