Vấn đềViệc cố gắng xuất một dictionary trong Python thành một chuỗi JSON nhưng lại khiến chương trình bị treo là một trải nghiệm quen thuộc của các nhà phát triển. Bạn sử dụng phương thức tiêu chuẩn json.dumps(), nhưng thay vì nhận được một chuỗi sạch sẽ, bạn lại gặp một stack trace:
TypeError: Object of type datetime is not JSON serializable
Lỗi này thường xảy ra ngay khi bạn đang gửi dữ liệu tới một API hoặc lưu bản ghi vào đĩa. Bạn rất có thể sẽ gặp phải trở ngại này khi truy xuất các hàng từ PostgreSQL, MySQL hoặc MongoDB, nơi các cột timestamp tự động được chuyển đổi thành các đối tượng datetime của Python.
Mã ví dụ gây ra lỗi```
import json from datetime import datetime
data = { "id": 1024, "event": "User Signup", "timestamp": datetime.now() # Đối tượng này gây ra lỗi treo chương trình }
Dòng này sẽ gây ra lỗi TypeError
json_data = json.dumps(data)
## Tại sao điều này xảy raModule `json` tích hợp sẵn của Python hoạt động hiệu quả nhưng khá "khó tính". Theo mặc định, nó chỉ hiểu cách ánh xạ các kiểu dữ liệu cơ bản có tương đương trực tiếp trong JSON, chẳng hạn như chuỗi (string), số nguyên (integer), số thực (float) và kiểu logic (boolean). Bản thân JSON không có kiểu 'Date' gốc. Vì đặc tả không xác định ngày tháng nên là một chuỗi hay một con số, Python từ chối đoán thay bạn. Nó chỉ đơn giản là dừng quá trình lại.
## Cách khắc phục### Cách 1: Khắc phục nhanh (Sử dụng đối số 'default')Đối với các script nhỏ, hãy truyền một hàm vào tham số `default` trong `json.dumps()`. Điều này cho trình mã hóa biết phải làm gì khi gặp một đối tượng mà nó không nhận diện được.
import json from datetime import datetime
data = {"timestamp": datetime.now()}
Ép kiểu bất kỳ đối tượng nào không thể tuần tự hóa thành chuỗi
json_data = json.dumps(data, default=str)
print(json_data)
Đầu ra: {"timestamp": "2024-06-17 14:30:05.123456"}
Mặc dù `default=str` rất nhanh, nhưng nó giống như một công cụ thô sơ. Bạn có thể thích một định dạng chuẩn hóa hơn như ISO 8601.
### Cách 2: Custom JSON Encoder (Khuyến nghị cho môi trường Production)Nếu bạn đang xây dựng một ứng dụng lớn hơn, một class encoder có thể tái sử dụng là lựa chọn tối ưu. Nó giữ cho logic tuần tự hóa (serialization) tập trung tại một nơi và đảm bảo tính nhất quán trong toàn bộ API của bạn.
import json from datetime import datetime, date
class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (datetime, date)): return obj.isoformat() return super().default(obj)
data = { "created_at": datetime.now(), "is_active": True }
Sử dụng đối số 'cls' để áp dụng logic tùy chỉnh của bạn
json_data = json.dumps(data, cls=DateTimeEncoder)
print(json_data)
### Cách 3: Tiền xử lý dữ liệuĐôi khi bạn không muốn thay đổi cách hoạt động của encoder. Thay vào đó, bạn có thể chuyển đổi dictionary một cách thủ công trước khi truyền nó vào hàm dump. Cách này thường an toàn hơn khi bạn chỉ có một hoặc hai trường ngày tháng.
from datetime import datetime import json
data = {"last_login": datetime.now()}
Tạo một bản sao nông (shallow copy) và định dạng trường cụ thể
clean_data = {**data, "last_login": data["last_login"].strftime('%Y-%m-%d %H:%M:%S')}
json_output = json.dumps(clean_data)
### Cách 4: Sử dụng Pydantic (Python hiện đại)Nếu bạn sử dụng FastAPI hoặc các công cụ kiểm định dữ liệu hiện đại, có thể bạn đã cài đặt **Pydantic**. Nó xử lý các trường hợp tuần tự hóa phức tạp một cách mặc định mà không cần cấu hình thêm.
from pydantic import BaseModel from datetime import datetime
class Event(BaseModel): name: str timestamp: datetime
event = Event(name="System Update", timestamp=datetime.now())
Pydantic v2 sử dụng model_dump_json()
print(event.model_dump_json())
## Các bước kiểm traSau khi áp dụng bản sửa lỗi, hãy chạy các bước kiểm tra sau để đảm bảo dữ liệu của bạn thực sự có thể sử dụng được:
- Xác nhận script kết thúc mà không gây ra lỗi `TypeError`.- Đảm bảo chuỗi ngày tháng ở đầu ra trông giống như `"2024-06-17T10:00:00"` thay vì một chuỗi đối tượng thô.- Xác minh JSON hợp lệ bằng cách thử tải ngược lại nó:```
try:
json.loads(json_data)
print("JSON hợp lệ và sẵn sàng để sử dụng.")
except Exception as e:
print(f"Kiểm tra thất bại: {e}")

