1"""
2Serialize data to/from JSON
3"""
4
5import datetime
6import decimal
7import json
8import uuid
9
10from django.core.serializers.base import DeserializationError
11from django.core.serializers.python import Deserializer as PythonDeserializer
12from django.core.serializers.python import Serializer as PythonSerializer
13from django.utils.duration import duration_iso_string
14from django.utils.functional import Promise
15from django.utils.timezone import is_aware
16
17
18class Serializer(PythonSerializer):
19 """Convert a queryset to JSON."""
20
21 internal_use_only = False
22
23 def _init_options(self):
24 self._current = None
25 self.json_kwargs = self.options.copy()
26 self.json_kwargs.pop("stream", None)
27 self.json_kwargs.pop("fields", None)
28 if self.options.get("indent"):
29 # Prevent trailing spaces
30 self.json_kwargs["separators"] = (",", ": ")
31 self.json_kwargs.setdefault("cls", DjangoJSONEncoder)
32 self.json_kwargs.setdefault("ensure_ascii", False)
33
34 def start_serialization(self):
35 self._init_options()
36 self.stream.write("[")
37
38 def end_serialization(self):
39 if self.options.get("indent"):
40 self.stream.write("\n")
41 self.stream.write("]")
42 if self.options.get("indent"):
43 self.stream.write("\n")
44
45 def end_object(self, obj):
46 # self._current has the field data
47 indent = self.options.get("indent")
48 if not self.first:
49 self.stream.write(",")
50 if not indent:
51 self.stream.write(" ")
52 if indent:
53 self.stream.write("\n")
54 json.dump(self.get_dump_object(obj), self.stream, **self.json_kwargs)
55 self._current = None
56
57 def getvalue(self):
58 # Grandparent super
59 return super(PythonSerializer, self).getvalue()
60
61
62class Deserializer(PythonDeserializer):
63 """Deserialize a stream or string of JSON data."""
64
65 def __init__(self, stream_or_string, **options):
66 if not isinstance(stream_or_string, (bytes, str)):
67 stream_or_string = stream_or_string.read()
68 if isinstance(stream_or_string, bytes):
69 stream_or_string = stream_or_string.decode()
70 try:
71 objects = json.loads(stream_or_string)
72 except Exception as exc:
73 raise DeserializationError() from exc
74 super().__init__(objects, **options)
75
76 def _handle_object(self, obj):
77 try:
78 yield from super()._handle_object(obj)
79 except (GeneratorExit, DeserializationError):
80 raise
81 except Exception as exc:
82 raise DeserializationError(f"Error deserializing object: {exc}") from exc
83
84
85class DjangoJSONEncoder(json.JSONEncoder):
86 """
87 JSONEncoder subclass that knows how to encode date/time, decimal types, and
88 UUIDs.
89 """
90
91 def default(self, o):
92 # See "Date Time String Format" in the ECMA-262 specification.
93 if isinstance(o, datetime.datetime):
94 r = o.isoformat()
95 if o.microsecond:
96 r = r[:23] + r[26:]
97 if r.endswith("+00:00"):
98 r = r.removesuffix("+00:00") + "Z"
99 return r
100 elif isinstance(o, datetime.date):
101 return o.isoformat()
102 elif isinstance(o, datetime.time):
103 if is_aware(o):
104 raise ValueError("JSON can't represent timezone-aware times.")
105 r = o.isoformat()
106 if o.microsecond:
107 r = r[:12]
108 return r
109 elif isinstance(o, datetime.timedelta):
110 return duration_iso_string(o)
111 elif isinstance(o, (decimal.Decimal, uuid.UUID, Promise)):
112 return str(o)
113 else:
114 return super().default(o)