-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathtest_json_serialization.py
More file actions
339 lines (272 loc) · 11.2 KB
/
test_json_serialization.py
File metadata and controls
339 lines (272 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import dataclasses
from collections import namedtuple
from types import SimpleNamespace
import pytest
from durabletask.internal.shared import (
AUTO_SERIALIZED,
from_json,
to_json,
)
# --- Dataclass fixtures ---
@dataclasses.dataclass
class SimpleData:
x: int
y: str
@dataclasses.dataclass
class InnerData:
value: int
@dataclasses.dataclass
class OuterData:
inner: InnerData
label: str
@dataclasses.dataclass
class DeeplyNested:
outer: OuterData
flag: bool
# --- Namedtuple fixtures ---
Point = namedtuple("Point", ["x", "y"])
class TestDataclassSerialization:
"""Tests for dataclass serialization/deserialization via to_json/from_json."""
def test_simple_dataclass_round_trip(self):
"""A simple dataclass should serialize and deserialize to a SimpleNamespace."""
obj = SimpleData(x=1, y="hello")
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert result.x == 1
assert result.y == "hello"
def test_simple_dataclass_json_contains_auto_serialized_marker(self):
"""The JSON output should contain the AUTO_SERIALIZED marker."""
obj = SimpleData(x=1, y="hello")
json_str = to_json(obj)
assert AUTO_SERIALIZED in json_str
def test_nested_dataclass_round_trip(self):
"""Nested dataclasses should all deserialize to SimpleNamespace, not dict."""
obj = OuterData(inner=InnerData(value=42), label="test")
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert isinstance(result.inner, SimpleNamespace), (
"Inner dataclass should deserialize to SimpleNamespace, not dict"
)
assert result.inner.value == 42
assert result.label == "test"
def test_deeply_nested_dataclass_round_trip(self):
"""Deeply nested dataclasses should all deserialize to SimpleNamespace."""
obj = DeeplyNested(
outer=OuterData(inner=InnerData(value=7), label="deep"),
flag=True,
)
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert isinstance(result.outer, SimpleNamespace)
assert isinstance(result.outer.inner, SimpleNamespace)
assert result.outer.inner.value == 7
assert result.outer.label == "deep"
assert result.flag is True
def test_dataclass_inside_dict(self):
"""A dataclass value inside a dict should round-trip as SimpleNamespace."""
obj = {"data": SimpleData(x=10, y="world")}
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, dict)
assert isinstance(result["data"], SimpleNamespace)
assert result["data"].x == 10
assert result["data"].y == "world"
def test_dataclass_inside_list(self):
"""Dataclass items inside a list should round-trip as SimpleNamespace."""
items = [SimpleData(x=1, y="a"), SimpleData(x=2, y="b")]
json_str = to_json(items)
result = from_json(json_str)
assert isinstance(result, list)
assert len(result) == 2
for item in result:
assert isinstance(item, SimpleNamespace)
assert result[0].x == 1
assert result[1].y == "b"
def test_array_of_nested_dataclasses(self):
"""An array of dataclasses with nested dataclass fields should fully round-trip."""
items = [
OuterData(inner=InnerData(value=1), label="first"),
OuterData(inner=InnerData(value=2), label="second"),
]
json_str = to_json(items)
result = from_json(json_str)
assert isinstance(result, list)
assert len(result) == 2
for item in result:
assert isinstance(item, SimpleNamespace)
assert isinstance(item.inner, SimpleNamespace)
assert result[0].inner.value == 1
assert result[0].label == "first"
assert result[1].inner.value == 2
assert result[1].label == "second"
def test_nested_array_of_dataclasses(self):
"""An array nested inside another array of dataclasses should round-trip."""
items = [
[SimpleData(x=1, y="a"), SimpleData(x=2, y="b")],
[SimpleData(x=3, y="c")],
]
json_str = to_json(items)
result = from_json(json_str)
assert isinstance(result, list)
assert len(result) == 2
assert isinstance(result[0], list)
assert len(result[0]) == 2
assert isinstance(result[1], list)
assert len(result[1]) == 1
for sublist in result:
for item in sublist:
assert isinstance(item, SimpleNamespace)
assert result[0][0].x == 1
assert result[0][1].y == "b"
assert result[1][0].x == 3
def test_dict_with_nested_dataclass_values(self):
"""Dict values that are nested dataclasses should fully round-trip."""
obj = {"item": OuterData(inner=InnerData(value=99), label="nested")}
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, dict)
assert isinstance(result["item"], SimpleNamespace)
assert isinstance(result["item"].inner, SimpleNamespace)
assert result["item"].inner.value == 99
assert result["item"].label == "nested"
def test_dict_with_multiple_dataclass_values(self):
"""A dict with several dataclass values should all round-trip."""
obj = {
"a": SimpleData(x=1, y="one"),
"b": SimpleData(x=2, y="two"),
}
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, dict)
for key in ("a", "b"):
assert isinstance(result[key], SimpleNamespace)
assert result["a"].x == 1
assert result["b"].y == "two"
def test_dict_with_array_of_dataclasses(self):
"""A dict whose value is a list of dataclasses should round-trip."""
obj = {"items": [SimpleData(x=1, y="a"), SimpleData(x=2, y="b")]}
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, dict)
assert isinstance(result["items"], list)
assert len(result["items"]) == 2
for item in result["items"]:
assert isinstance(item, SimpleNamespace)
assert result["items"][0].x == 1
assert result["items"][1].y == "b"
def test_dict_with_array_of_nested_dataclasses(self):
"""A dict whose value is a list of nested dataclasses should fully round-trip."""
obj = {
"records": [
OuterData(inner=InnerData(value=10), label="r1"),
OuterData(inner=InnerData(value=20), label="r2"),
]
}
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, dict)
assert isinstance(result["records"], list)
for item in result["records"]:
assert isinstance(item, SimpleNamespace)
assert isinstance(item.inner, SimpleNamespace)
assert result["records"][0].inner.value == 10
assert result["records"][1].label == "r2"
def test_dataclass_with_list_of_dataclass_field(self):
"""A dataclass containing a list-of-dataclass field should round-trip."""
@dataclasses.dataclass
class Container:
items: list
obj = Container(items=[InnerData(value=1), InnerData(value=2)])
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert isinstance(result.items, list)
assert len(result.items) == 2
for item in result.items:
assert isinstance(item, SimpleNamespace)
assert result.items[0].value == 1
assert result.items[1].value == 2
def test_dataclass_with_dict_of_dataclass_field(self):
"""A dataclass containing a dict-of-dataclass field should round-trip."""
@dataclasses.dataclass
class Mapping:
entries: dict
obj = Mapping(entries={"a": InnerData(value=5), "b": InnerData(value=6)})
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert isinstance(result.entries, dict)
for val in result.entries.values():
assert isinstance(val, SimpleNamespace)
assert result.entries["a"].value == 5
assert result.entries["b"].value == 6
class TestSimpleNamespaceSerialization:
"""Tests for SimpleNamespace serialization."""
def test_simple_namespace_round_trip(self):
"""SimpleNamespace should serialize and deserialize correctly."""
obj = SimpleNamespace(a=1, b="two")
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert result.a == 1
assert result.b == "two"
def test_simple_namespace_not_mutated(self):
"""Serializing a SimpleNamespace should NOT mutate the original object."""
obj = SimpleNamespace(x=1, y=2)
original_attrs = set(vars(obj).keys())
to_json(obj)
current_attrs = set(vars(obj).keys())
assert current_attrs == original_attrs, (
f"Original SimpleNamespace was mutated: gained {current_attrs - original_attrs}"
)
assert not hasattr(obj, AUTO_SERIALIZED)
def test_nested_simple_namespace_round_trip(self):
"""Nested SimpleNamespace should deserialize as SimpleNamespace."""
obj = SimpleNamespace(child=SimpleNamespace(val=99))
json_str = to_json(obj)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert isinstance(result.child, SimpleNamespace)
assert result.child.val == 99
class TestNamedtupleSerialization:
"""Tests for namedtuple serialization."""
def test_namedtuple_top_level_round_trip(self):
"""A namedtuple at the top level should serialize with field names."""
p = Point(x=3, y=4)
json_str = to_json(p)
result = from_json(json_str)
assert isinstance(result, SimpleNamespace)
assert result.x == 3
assert result.y == 4
class TestPrimitiveSerialization:
"""Tests for primitive/basic type round-trips."""
@pytest.mark.parametrize("value", [
42,
3.14,
"hello",
True,
False,
None,
[1, 2, 3],
{"key": "value"},
])
def test_primitive_round_trip(self, value):
"""Primitive types should round-trip unchanged."""
json_str = to_json(value)
result = from_json(json_str)
assert result == value
class TestDataclassNotMutated:
"""Ensure serialization does not mutate dataclass inputs."""
def test_dataclass_not_mutated(self):
"""Serializing a dataclass should not add attributes to the original."""
obj = SimpleData(x=1, y="test")
to_json(obj)
# dataclass fields should be unchanged
assert obj.x == 1
assert obj.y == "test"
assert not hasattr(obj, AUTO_SERIALIZED)