Flask có jsonify() sẵn, nhưng định dạng mặc định của nó không thân thiện với frontend: datetime bị chuyển thành chuỗi kiểu Mon, 02 Nov 2026 14:00:00 GMT, còn Decimal thành chuỗi "129900.50" thay vì số. Giải pháp là viết một custom JSON provider kế thừa DefaultJSONProvider, chuyển datetime sang ISO 8601 và Decimal sang float, rồi gắn vào app qua json_provider_class. Cách này áp dụng cho mọi API Flask trả dữ liệu từ database có cột ngày giờ hoặc tiền tệ.
Vấn đề
Giả sử endpoint trả về một đơn hàng lấy từ database:
from datetime import datetimefrom decimal import Decimalfrom flask import Flask, jsonifyapp = Flask(__name__)@app.get("/order/<int:order_id>")def order(order_id): return jsonify({ "id": order_id, "total": Decimal("129900.50"), "created_at": datetime(2026, 11, 2, 14, 0, 0), })
Kết quả trả về không như bạn tưởng:
{"created_at": "Mon, 02 Nov 2026 14:00:00 GMT", "id": 1, "total": "129900.50"}
Frontend nhận total dạng chuỗi nên phải tự ép kiểu trước khi tính toán, còn chuỗi ngày kiểu RFC 1123 thì không phải thư viện nào cũng parse sẵn như ISO 8601. Lỗi không nằm ở dữ liệu mà ở khâu serialize: jsonify() dùng provider mặc định của Flask, và bạn chưa dạy nó định dạng bạn muốn.
Giải pháp
Viết provider riêng, chỉ override phương thức default() cho hai kiểu dữ liệu cần xử lý, còn lại để lớp cha lo:
from datetime import date, datetimefrom decimal import Decimalfrom flask import Flask, jsonifyfrom flask.json.provider import DefaultJSONProviderclass CustomJSONProvider(DefaultJSONProvider): def default(self, obj): if isinstance(obj, (datetime, date)): return obj.isoformat() if isinstance(obj, Decimal): return float(obj) return super().default(obj)class MyFlask(Flask): json_provider_class = CustomJSONProviderapp = MyFlask(__name__)@app.get("/order/<int:order_id>")def order(order_id): return jsonify({ "id": order_id, "total": Decimal("129900.50"), "created_at": datetime(2026, 11, 2, 14, 0, 0), })
Giờ response đã đúng định dạng:
{"created_at": "2026-11-02T14:00:00", "id": 1, "total": 129900.5}
Lưu ý quan trọng: phải khai báo json_provider_class trên lớp con của Flask, hoặc gán app.json = CustomJSONProvider(app) ngay sau khi tạo app. Gán app.json_provider_class = ... sau khi app đã tạo sẽ không có tác dụng, vì provider đã được khởi tạo trong __init__. Đây là lỗi dễ gặp nhất khi làm theo hướng dẫn này, và nó khiến bạn tưởng code không chạy dù không báo lỗi gì.
Áp dụng vào dự án của bạn
Nếu dự án dùng Application Factory, hãy đặt lớp MyFlask trong cùng module tạo app và dùng nó thay cho Flask trong create_app() — mọi blueprint đều hưởng định dạng chung mà không cần sửa từng endpoint. Với tiền tệ, cân nhắc kỹ trước khi chuyển Decimal sang float: nếu hệ thống cần chính xác tuyệt đối từng đồng, hãy giữ Decimal dạng chuỗi và để frontend hiển thị, chỉ dùng float khi chấp nhận sai số thập phân nhỏ.
Bài viết liên quan
- Flask: đọc và validate JSON body đúng cách — hết cảnh API trả lỗi HTML với request.get_json() trong 5 phút
- Flask: dùng Application Factory (create_app) để app dễ test, dễ mở rộng ngay từ đầu
- Flask: dùng current_app thay vì import app trực tiếp — hết lỗi circular import trong 5 phút
Câu hỏi thường gặp
Vì sao không chuyển datetime thành chuỗi ngay trong view?
Bạn hoàn toàn có thể làm vậy, nhưng sẽ phải lặp lại ở mọi endpoint và dễ quên một chỗ. Provider tập trung xử lý một lần cho toàn bộ app, view chỉ cần trả đúng object.
Decimal chuyển sang float có mất chính xác không?
Có thể, vì float là số thực nhị phân. Với giá hiển thị hoặc thống kê thì chấp nhận được; với thanh toán, đối soát, hãy giữ chuỗi hoặc dùng đơn vị nhỏ nhất (ví dụ số xu) thay vì số thập phân.
Gán app.json_provider_class sau khi tạo app có được không?
Không. Provider đã được tạo trong __init__ của app nên gán thuộc tính này về sau không thay thế instance đang chạy. Hãy khai báo trên lớp con của Flask hoặc gán trực tiếp app.json.
Kiểu dữ liệu lạ khác, ví dụ UUID, xử lý thế nào?
Thêm một nhánh isinstance trong default() và trả về dạng bạn muốn, ví dụ str(obj) cho UUID. Mọi kiểu không xử lý sẽ rơi về super().default() và báo lỗi như cũ nếu không serialize được.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Flask Documentation — JSON: https://flask.palletsprojects.com/en/stable/api/#flask.json.provider.DefaultJSONProvider
- Flask Documentation — Application object (json_provider_class): https://flask.palletsprojects.com/en/stable/api/#flask.Flask.json_provider_class
