diff --git a/README.md b/README.md index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_UkVBRE1FLm1k..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_UkVBRE1FLm1k 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,8 @@ to the standard library. It can be disabled with `orjson.OPT_PASSTHROUGH_SUBCLASS`.`dataclasses.dataclass` instances are now serialized by default and cannot be customized in a -`default` function. `uuid.UUID` instances are serialized by default. +`default` function unless `option=orjson.OPT_PASSTHROUGH_DATACLASS` is +specified. `uuid.UUID` instances are serialized by default. For any type that is now serialized, implementations in a `default` function and options enabling them can be removed but do not need to be. There was no change in deserialization. @@ -404,6 +405,38 @@ b'"1970-01-01T00:00:00"' ``` +##### OPT_PASSTHROUGH_DATACLASS + +Passthrough `dataclasses.dataclass` instances to `default`. This allows +customizing their output but is much slower. + + +```python +>>> import orjson, dataclasses +>>> +@dataclasses.dataclass +class User: + id: str + name: str + password: str + +def default(obj): + if isinstance(obj, User): + return {"id": obj.id, "name": obj.name} + raise TypeError + +>>> orjson.dumps(User("3b1", "asd", "zxc")) +b'{"id":"3b1","name":"asd","password":"zxc"}' +>>> orjson.dumps(User("3b1", "asd", "zxc"), option=orjson.OPT_PASSTHROUGH_DATACLASS) +TypeError: Type is not JSON serializable: User +>>> orjson.dumps( + User("3b1", "asd", "zxc"), + option=orjson.OPT_PASSTHROUGH_DATACLASS, + default=default, + ) +b'{"id":"3b1","name":"asd"}' +``` + ##### OPT_PASSTHROUGH_DATETIME Passthrough `datetime.datetime`, `datetime.date`, and `datetime.time` instances diff --git a/orjson.pyi b/orjson.pyi index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_b3Jqc29uLnB5aQ==..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_b3Jqc29uLnB5aQ== 100644 --- a/orjson.pyi +++ b/orjson.pyi @@ -17,6 +17,7 @@ OPT_NAIVE_UTC: int OPT_NON_STR_KEYS: int OPT_OMIT_MICROSECONDS: int +OPT_PASSTHROUGH_DATACLASS: int OPT_PASSTHROUGH_DATETIME: int OPT_PASSTHROUGH_SUBCLASS: int OPT_SERIALIZE_DATACLASS: int diff --git a/src/lib.rs b/src/lib.rs index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_c3JjL2xpYi5ycw==..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_c3JjL2xpYi5ycw== 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,11 @@ opt!(mptr, "OPT_OMIT_MICROSECONDS\0", opt::OMIT_MICROSECONDS); opt!( mptr, + "OPT_PASSTHROUGH_DATACLASS\0", + opt::PASSTHROUGH_DATACLASS + ); + opt!( + mptr, "OPT_PASSTHROUGH_DATETIME\0", opt::PASSTHROUGH_DATETIME ); diff --git a/src/opt.rs b/src/opt.rs index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_c3JjL29wdC5ycw==..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_c3JjL29wdC5ycw== 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -13,6 +13,7 @@ pub const PASSTHROUGH_SUBCLASS: Opt = 1 << 8; pub const PASSTHROUGH_DATETIME: Opt = 1 << 9; pub const APPEND_NEWLINE: Opt = 1 << 10; +pub const PASSTHROUGH_DATACLASS: Opt = 1 << 11; // deprecated pub const SERIALIZE_DATACLASS: Opt = 0; @@ -20,7 +21,8 @@ pub const SORT_OR_NON_STR_KEYS: Opt = SORT_KEYS | NON_STR_KEYS; -pub const NOT_PASSTHROUGH: Opt = !(PASSTHROUGH_DATETIME | PASSTHROUGH_SUBCLASS); +pub const NOT_PASSTHROUGH: Opt = + !(PASSTHROUGH_DATETIME | PASSTHROUGH_DATACLASS | PASSTHROUGH_SUBCLASS); pub const MAX_OPT: i32 = (APPEND_NEWLINE | INDENT_2 @@ -28,6 +30,7 @@ | NON_STR_KEYS | OMIT_MICROSECONDS | PASSTHROUGH_DATETIME + | PASSTHROUGH_DATACLASS | PASSTHROUGH_SUBCLASS | SERIALIZE_DATACLASS | SERIALIZE_NUMPY diff --git a/src/serialize/encode.rs b/src/serialize/encode.rs index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_c3JjL3NlcmlhbGl6ZS9lbmNvZGUucnM=..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_c3JjL3NlcmlhbGl6ZS9lbmNvZGUucnM= 100644 --- a/src/serialize/encode.rs +++ b/src/serialize/encode.rs @@ -143,7 +143,9 @@ && opts & PASSTHROUGH_SUBCLASS == 0 { ObType::Dict - } else if ffi!(PyDict_Contains((*ob_type).tp_dict, DATACLASS_FIELDS_STR)) == 1 { + } else if opts & PASSTHROUGH_DATACLASS == 0 + && ffi!(PyDict_Contains((*ob_type).tp_dict, DATACLASS_FIELDS_STR)) == 1 + { ObType::Dataclass } else if opts & SERIALIZE_NUMPY != 0 && is_numpy_scalar(ob_type) { ObType::NumpyScalar diff --git a/test/test_api.py b/test/test_api.py index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_dGVzdC90ZXN0X2FwaS5weQ==..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_dGVzdC90ZXN0X2FwaS5weQ== 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -93,7 +93,7 @@ dumps() option out of range high """ with self.assertRaises(orjson.JSONEncodeError): - orjson.dumps(True, option=1 << 11) + orjson.dumps(True, option=1 << 12) def test_opts_multiple(self): """ diff --git a/test/test_dataclass.py b/test/test_dataclass.py index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_dGVzdC90ZXN0X2RhdGFjbGFzcy5weQ==..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_dGVzdC90ZXN0X2RhdGFjbGFzcy5weQ== 100644 --- a/test/test_dataclass.py +++ b/test/test_dataclass.py @@ -2,7 +2,7 @@ import unittest import uuid -from dataclasses import InitVar, dataclass, field +from dataclasses import InitVar, asdict, dataclass, field from enum import Enum from typing import ClassVar, Dict, Optional @@ -250,3 +250,37 @@ orjson.dumps(obj, option=orjson.OPT_SERIALIZE_DATACLASS), b'{"name":"a","number":1,"sub":null}', ) + + +class DataclassPassthroughTests(unittest.TestCase): + def test_dataclass_passthrough_raise(self): + """ + dumps() dataclass passes to default with OPT_PASSTHROUGH_DATACLASS + """ + obj = Dataclass1("a", 1, None) + with self.assertRaises(orjson.JSONEncodeError): + orjson.dumps(obj, option=orjson.OPT_PASSTHROUGH_DATACLASS) + with self.assertRaises(orjson.JSONEncodeError): + orjson.dumps( + InitDataclass("zxc", "vbn"), option=orjson.OPT_PASSTHROUGH_DATACLASS + ) + + def test_dataclass_passthrough_default(self): + """ + dumps() dataclass passes to default with OPT_PASSTHROUGH_DATACLASS + """ + obj = Dataclass1("a", 1, None) + self.assertEqual( + orjson.dumps(obj, option=orjson.OPT_PASSTHROUGH_DATACLASS, default=asdict), + b'{"name":"a","number":1,"sub":null}', + ) + + def default(obj): + if isinstance(obj, Dataclass1): + return {"name": obj.name, "number": obj.number} + raise TypeError + + self.assertEqual( + orjson.dumps(obj, option=orjson.OPT_PASSTHROUGH_DATACLASS, default=default), + b'{"name":"a","number":1}', + ) diff --git a/test/test_numpy.py b/test/test_numpy.py index b0c889bccb37e811fbb6b3d3b42f9be5d956f38d_dGVzdC90ZXN0X251bXB5LnB5..ac110ee6b3e99b7c9633ee963d01c0780f5f2630_dGVzdC90ZXN0X251bXB5LnB5 100644 --- a/test/test_numpy.py +++ b/test/test_numpy.py @@ -2,4 +2,6 @@ import unittest +import pytest + import orjson @@ -5,5 +7,4 @@ import orjson -import pytest try: import numpy