11import logging
22from decimal import Decimal
33from functools import lru_cache
4- from typing import Dict , List
4+ from typing import Dict , List , Tuple , Any
55
66from django .core .serializers import serialize
77from django .db .models import (
@@ -141,7 +141,7 @@ def _json_serializer(obj):
141141 raise TypeError
142142
143143
144- def _fix_floats (current : Dict , data : Dict = None , paths : List = [] ) -> None :
144+ def _fix_floats (current : Dict , data : Dict = None , paths : List = None ) -> None :
145145 """
146146 Recursively change any Python floats to a string so that JavaScript
147147 won't convert the float to an integer when deserializing.
@@ -153,6 +153,9 @@ def _fix_floats(current: Dict, data: Dict = None, paths: List = []) -> None:
153153 if data is None :
154154 data = current
155155
156+ if paths is None :
157+ paths = []
158+
156159 if isinstance (current , dict ):
157160 for key , val in current .items ():
158161 paths .append (key )
@@ -176,32 +179,76 @@ def _fix_floats(current: Dict, data: Dict = None, paths: List = []) -> None:
176179
177180
178181@lru_cache (maxsize = 128 , typed = True )
179- def _dumps (serialized_data ):
182+ def _dumps (
183+ serialized_data : bytes , exclude_field_attributes : Tuple [str ] = None
184+ ) -> bytes :
185+ """
186+ Dump serialized data with custom massaging to fix floats and remove specific keys as needed.
187+ """
188+
180189 dict_data = orjson .loads (serialized_data )
181190 _fix_floats (dict_data )
191+ _exclude_field_attributes (dict_data , exclude_field_attributes )
182192
183- dumped_data = orjson .dumps (dict_data ). decode ( "utf-8" )
193+ dumped_data = orjson .dumps (dict_data )
184194 return dumped_data
185195
186196
187- def dumps (data : dict , fix_floats : bool = True ) -> str :
197+ def _exclude_field_attributes (
198+ dict_data : Dict [Any , Any ], exclude_field_attributes : Tuple [str ] = None
199+ ) -> None :
200+ """
201+ Remove the field attribute from `dict_data`. Handles nested attributes with a dot.
202+
203+ Example:
204+ _exclude_field_attributes({"1": {"2": {"3": "4"}}}, ("1.2.3",)) == {"1": {"2": {}}}
205+ """
206+
207+ if exclude_field_attributes :
208+ for field in exclude_field_attributes :
209+ field_splits = field .split ("." )
210+
211+ if len (field_splits ) > 2 :
212+ remaining_field_attributes = field [field .index ("." ) + 1 :]
213+ remaining_dict_data = dict_data [field_splits [0 ]]
214+
215+ return _exclude_field_attributes (
216+ remaining_dict_data , (remaining_field_attributes ,)
217+ )
218+ elif len (field_splits ) == 2 :
219+ (field_name , field_attr ) = field_splits
220+ del dict_data [field_name ][field_attr ]
221+
222+
223+ def dumps (
224+ data : Dict , fix_floats : bool = True , exclude_field_attributes : Tuple [str ] = None
225+ ) -> str :
188226 """
189227 Converts the passed-in dictionary to a string representation.
190228
191229 Handles the following objects: dataclass, datetime, enum, float, int, numpy, str, uuid,
192- Django Model, Django QuerySet, any object with `to_json` method.
230+ Django Model, Django QuerySet, Pydantic models (`PydanticBaseModel`), any object with `to_json` method.
193231
194232 Args:
195233 param fix_floats: Whether any floats should be converted to strings. Defaults to `True`,
196234 but will be faster without it.
197-
198- Returns a string which deviates from `orjson.dumps`, but seems more useful.
235+ param exclude_field_attributes: Tuple of strings with field attributes to remove, i.e. "1.2"
236+ to remove the key `2` from `{"1": {"2": "3"}}`
237+
238+ Returns a `str` instead of `bytes` (which deviates from `orjson.dumps`), but seems more useful.
199239 """
200240
201241 serialized_data = orjson .dumps (data , default = _json_serializer )
202242
203243 if fix_floats :
204- return _dumps (serialized_data )
244+ # Handle excluded field attributes in `_dumps` to reduce the amount of serialization/deserialization needed
245+ serialized_data = _dumps (
246+ serialized_data , exclude_field_attributes = exclude_field_attributes
247+ )
248+ elif exclude_field_attributes :
249+ dict_data = orjson .loads (serialized_data )
250+ _exclude_field_attributes (dict_data , exclude_field_attributes )
251+ serialized_data = orjson .dumps (dict_data )
205252
206253 return serialized_data .decode ("utf-8" )
207254
0 commit comments