Python chuyển JSON sang CSV — Ví dụ DictWriter + pandas

·Backend Developer·Đánh giá bởiPriya Sharma·Đã xuất bản

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.

Before · json
After · json
[{"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.

Python 3.8+ — ví dụ tối giản json to csv
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.95

Tham 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.

Python 3.8+ — đọc file JSON, ghi file CSV
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.

Lưu ý: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.

Python 3.8+ — tiền xử lý datetime và Decimal trước khi ghi CSV
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 GmbH

Hà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.

Cảnh báo:Nếu bạn để giá trị 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.

Tham số
Kiểu
Mặc định
Mô tả
f
file object
(bắt buộc)
Bất kỳ đối tượng nào có phương thức write() — thường lấy từ open()
fieldnames
sequence
(bắt buộc)
Danh sách khóa xác định thứ tự cột trong đầu ra CSV
restval
str
""
Giá trị được ghi khi một dict thiếu khóa từ fieldnames
extrasaction
str
"raise"
"raise" ném ValueError nếu có khóa thừa; "ignore" bỏ qua chúng
dialect
str / Dialect
"excel"
Quy tắc định dạng có sẵn — "excel", "excel-tab", hoặc "unix"
delimiter
str
","
Ký tự đơn phân tách các trường — dùng "\t" để xuất TSV
quotechar
str
"
Ký tự dùng để bao quanh các trường chứa dấu phân tách
quoting
int
csv.QUOTE_MINIMAL
Kiểm soát khi nào áp dụng dấu ngoặc — MINIMAL, ALL, NONNUMERIC, NONE
lineterminator
str
"\r\n"
Chuỗi thêm vào sau mỗi hàng — ghi đè thành "\n" để xuất kiểu Unix

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.

Python 3.8+ — pandas read_json rồi to_csv
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.

Python 3.8+ — làm phẳng JSON lồng nhau dùng json_normalize
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, total

Tham 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 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.

Tham số
Kiểu
Mặc định
Mô tả
path_or_buf
str / Path / None
None
Đường dẫn file hoặc buffer — None trả về CSV dưới dạng chuỗi
sep
str
","
Dấu phân tách trường — dùng "\t" cho TSV
index
bool
True
Ghi chỉ mục hàng làm cột đầu tiên — hầu như luôn đặt thành False
columns
list
None
Chọn và sắp xếp lại các cột trong đầu ra
header
bool / list
True
Ghi tên cột — đặt False khi nối vào file đã có
encoding
str
"utf-8"
Encoding đầu ra — dùng "utf-8-sig" để tương thích Excel trên Windows
na_rep
str
""
Chuỗi đại diện cho giá trị thiếu (NaN, None)
quoting
int
csv.QUOTE_MINIMAL
Kiểm soát khi nào các trường được bao ngoặc
Python 3.8+ — to_csv với các ghi đè tham số thông dụng
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

Python 3.8+ — chuyển đổi file JSON sang CSV với xử lý lỗi
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

Python 3.8+ — lấy JSON từ API và lưu dưới dạng CSV
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")
Lưu ý:Ví dụ trên dùng 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.

bash — chuyển đổi json sang csv một dòng lệnh
# 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
bash — script CLI độc lập với argparse
# 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)
" "$@"
bash — dùng jq + csvkit cho phép biến đổi phức tạp
# 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.csv

Miller (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.

bash — cài pyarrow
pip install pyarrow
Python 3.8+ — pandas với pyarrow để ghi CSV nhanh hơn
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.

bash — cài rich
pip install rich
Python 3.8+ — xem trước đầu ra CSV trong terminal với 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 terminal
Cảnh báo:Rich chỉ dùng để hiển thị trong terminal. Đừng dùng nó để tạo file CSV — nó thêm mã escape ANSI sẽ làm hỏng đầu ra. Ghi vào file bằng csv.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.

bash — cài ijson
pip install ijson

Stream mảng JSON sang CSV với ijson

Python 3.8+ — stream mảng JSON lớn sang CSV với bộ nhớ ổn định
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.

Python 3.8+ — chuyển đổi NDJSON sang CSV từng dòng
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")
Lưu ý:Chuyển sang streaming khi file JSON vượt quá 100 MB. Một mảng JSON 1 GB được tải bằng 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

Thiếu newline='' trong open() — hàng trống trên Windows

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.

Before · Python
After · Python
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 Windows
with 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ảng
Quên index=False trong pandas to_csv()

Vấ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'.

Before · Python
After · Python
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 parser
df = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# CSV sạch: event_id,timestamp,...
Dùng records[0].keys() khi các bản ghi có khóa không nhất quán

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.

Before · Python
After · Python
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
Ghi dict lồng nhau trực tiếp vào CSV mà không làm phẳng

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.

Before · Python
After · Python
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_region

csv.DictWriter vs pandas — So sánh nhanh

Phương pháp
JSON lồng nhau
Kiểu tùy chỉnh
Streaming
Phụ thuộc
Cần cài đặt
csv.DictWriter
✗ (flatten thủ công)
✓ (từng hàng)
Không có
Không (stdlib)
csv.writer
✓ (từng hàng)
Không có
Không (stdlib)
pd.DataFrame.to_csv()
✗ (chỉ flat)
✓ (qua dtypes)
pandas + numpy
pip install
pd.json_normalize() + to_csv()
✓ (qua dtypes)
pandas + numpy
pip install
csv.writer + json_flatten
flatten_json
pip install
jq + csvkit (CLI)
✓ (qua jq)
N/A
jq, csvkit
Cài hệ thống

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.

Python
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.

Python
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.

Python
# 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.

Python
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.

Python
# 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.

Python
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.1

Công cụ liên quan

MS
Maria SantosBackend Developer

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.

PS
Priya SharmaNgười đánh giá kỹ thuật

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.