diff --git a/CHANGES.rst b/CHANGES.rst index 394cc842..a1150437 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -38,6 +38,8 @@ Unreleased dependency to >= 0.15. :issue:`3022` - Support ``static_url_path`` that ends with a forward slash. :issue:`3134` +- :meth:`jsonify` supports :class:`dataclasses.dataclass` objects. + :pr:`3195` .. _#2935: https://github.com/pallets/flask/issues/2935 .. _#2957: https://github.com/pallets/flask/issues/2957 diff --git a/flask/json/__init__.py b/flask/json/__init__.py index ac9847dd..f3de8ef2 100644 --- a/flask/json/__init__.py +++ b/flask/json/__init__.py @@ -20,6 +20,10 @@ from jinja2 import Markup # depend anyways. from itsdangerous import json as _json +try: + import dataclasses +except ImportError: + dataclasses = None # Figure out if simplejson escapes slashes. This behavior was changed # from one version to another without reason. @@ -54,11 +58,15 @@ def _wrap_writer_for_text(fp, encoding): class JSONEncoder(_json.JSONEncoder): - """The default Flask JSON encoder. This one extends the default simplejson - encoder by also supporting ``datetime`` objects, ``UUID`` as well as - ``Markup`` objects which are serialized as RFC 822 datetime strings (same - as the HTTP date format). In order to support more data types override the - :meth:`default` method. + """The default Flask JSON encoder. This one extends the default + encoder by also supporting ``datetime``, ``UUID``, ``dataclasses``, + and ``Markup`` objects. + + ``datetime`` objects are serialized as RFC 822 datetime strings. + This is the same as the HTTP date format. + + In order to support more data types, override the :meth:`default` + method. """ def default(self, o): @@ -84,6 +92,8 @@ class JSONEncoder(_json.JSONEncoder): return http_date(o.timetuple()) if isinstance(o, uuid.UUID): return str(o) + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) if hasattr(o, "__html__"): return text_type(o.__html__()) return _json.JSONEncoder.default(self, o) diff --git a/tests/test_basic.py b/tests/test_basic.py index 1f41920e..86beeb12 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -10,6 +10,7 @@ """ import re +import sys import time import uuid from datetime import datetime @@ -1289,6 +1290,14 @@ def test_jsonify_mimetype(app, req_ctx): assert rv.mimetype == "application/vnd.api+json" +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") +def test_json_dump_dataclass(app, req_ctx): + from dataclasses import make_dataclass + Data = make_dataclass("Data", [("name", str)]) + value = flask.json.dumps(Data("Flask"), app=app) + value = flask.json.loads(value, app=app) + assert value == {"name": "Flask"} + def test_jsonify_args_and_kwargs_check(app, req_ctx): with pytest.raises(TypeError) as e: flask.jsonify("fake args", kwargs="fake")