Coverage for tests / unit / json_serialize / test_json_serialize.py: 98%

346 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-18 02:51 -0700

1"""Tests for muutils.json_serialize.json_serialize module. 

2 

3IMPORTANT: This tests the core json_serialize functionality. Array-specific tests are in test_array.py, 

4and utility function tests are in test_util.py. We focus on JsonSerializer class and handler system here. 

5""" 

6 

7from __future__ import annotations 

8 

9import warnings 

10from collections import namedtuple 

11from dataclasses import dataclass 

12from pathlib import Path 

13from typing import Any 

14 

15import pytest 

16 

17from muutils.errormode import ErrorMode 

18from muutils.json_serialize.json_serialize import ( 

19 BASE_HANDLERS, 

20 DEFAULT_HANDLERS, 

21 JsonSerializer, 

22 SerializerHandler, 

23 json_serialize, 

24) 

25from muutils.json_serialize.types import _FORMAT_KEY 

26from muutils.json_serialize.util import SerializationException 

27 

28 

29# ============================================================================ 

30# Test classes and fixtures 

31# ============================================================================ 

32 

33 

34@dataclass 

35class SimpleDataclass: 

36 """Simple dataclass for testing.""" 

37 

38 x: int 

39 y: str 

40 z: bool = True 

41 

42 

43@dataclass 

44class NestedDataclass: 

45 """Nested dataclass for testing.""" 

46 

47 simple: SimpleDataclass 

48 data: dict[str, Any] 

49 

50 

51class ClassWithSerialize: 

52 """Class with custom serialize method.""" 

53 

54 def __init__(self, value: int): 

55 self.value = value 

56 self.name = "test" 

57 

58 def serialize(self) -> dict: 

59 """Custom serialization.""" 

60 return {"custom_value": self.value * 2, "custom_name": self.name.upper()} 

61 

62 

63class UnserializableClass: 

64 """Class that can't be easily serialized.""" 

65 

66 def __init__(self): 

67 self.data = "test" 

68 

69 

70# ============================================================================ 

71# Tests for basic type serialization 

72# ============================================================================ 

73 

74 

75def test_json_serialize_basic_types(): 

76 """Test serialization of basic Python types: int, float, str, bool, None, list, dict.""" 

77 serializer = JsonSerializer() 

78 

79 # Test primitives 

80 assert serializer.json_serialize(42) == 42 

81 assert serializer.json_serialize(3.14) == 3.14 

82 assert serializer.json_serialize("hello") == "hello" 

83 assert serializer.json_serialize(True) is True 

84 assert serializer.json_serialize(False) is False 

85 assert serializer.json_serialize(None) is None 

86 

87 # Test list 

88 list_result = serializer.json_serialize([1, 2, 3]) 

89 assert list_result == [1, 2, 3] 

90 assert isinstance(list_result, list) 

91 

92 # Test dict 

93 dict_result = serializer.json_serialize({"a": 1, "b": 2}) 

94 assert dict_result == {"a": 1, "b": 2} 

95 assert isinstance(dict_result, dict) 

96 

97 # Test empty containers 

98 assert serializer.json_serialize([]) == [] 

99 assert serializer.json_serialize({}) == {} 

100 

101 

102def test_json_serialize_function(): 

103 """Test the module-level json_serialize function with default config.""" 

104 # Test that it works with basic types 

105 assert json_serialize(42) == 42 

106 assert json_serialize("test") == "test" 

107 assert json_serialize([1, 2, 3]) == [1, 2, 3] 

108 assert json_serialize({"key": "value"}) == {"key": "value"} 

109 

110 # Test with more complex types 

111 obj = SimpleDataclass(x=10, y="hello", z=False) 

112 result = json_serialize(obj) 

113 assert result == {"x": 10, "y": "hello", "z": False} 

114 

115 

116# ============================================================================ 

117# Tests for .serialize() method override 

118# ============================================================================ 

119 

120 

121def test_json_serialize_serialize_method(): 

122 """Test objects with .serialize() method are handled correctly.""" 

123 serializer = JsonSerializer() 

124 

125 obj = ClassWithSerialize(value=5) 

126 result = serializer.json_serialize(obj) 

127 assert isinstance(result, dict) 

128 

129 # Should use the custom serialize method 

130 assert result == {"custom_value": 10, "custom_name": "TEST"} 

131 assert result["custom_value"] == obj.value * 2 # ty: ignore[invalid-argument-type] 

132 assert result["custom_name"] == obj.name.upper() # ty: ignore[invalid-argument-type] 

133 

134 

135def test_serialize_method_priority(): 

136 """Test that .serialize() method takes priority over other handlers.""" 

137 serializer = JsonSerializer() 

138 

139 # Even though this is a dataclass, the .serialize() method should take priority 

140 @dataclass 

141 class DataclassWithSerialize: 

142 x: int 

143 y: int 

144 

145 def serialize(self) -> dict: 

146 return {"sum": self.x + self.y} 

147 

148 obj = DataclassWithSerialize(x=3, y=7) 

149 result = serializer.json_serialize(obj) 

150 assert isinstance(result, dict) 

151 

152 # Should use custom serialize, not dataclass handler 

153 assert result == {"sum": 10} 

154 assert "x" not in result 

155 assert "y" not in result 

156 

157 

158# ============================================================================ 

159# Tests for custom handlers 

160# ============================================================================ 

161 

162 

163def test_JsonSerializer_custom_handlers(): 

164 """Test adding custom pre/post handlers and verify execution order.""" 

165 # Create a custom handler that captures specific types 

166 custom_check_count = {"count": 0} 

167 custom_serialize_count = {"count": 0} 

168 

169 def custom_check(self, obj, path): 

170 custom_check_count["count"] += 1 

171 return isinstance(obj, str) and obj.startswith("CUSTOM:") 

172 

173 def custom_serialize(self, obj, path): 

174 custom_serialize_count["count"] += 1 

175 return {"custom": True, "value": obj[7:]} # Remove "CUSTOM:" prefix 

176 

177 custom_handler = SerializerHandler( 

178 check=custom_check, 

179 serialize_func=custom_serialize, 

180 uid="custom_string_handler", 

181 desc="Custom handler for strings starting with CUSTOM:", 

182 ) 

183 

184 # Create serializer with custom handler in handlers_pre (before defaults) 

185 serializer = JsonSerializer(handlers_pre=(custom_handler,)) 

186 

187 # Test that custom handler is used 

188 result = serializer.json_serialize("CUSTOM:test") 

189 assert result == {"custom": True, "value": "test"} 

190 assert custom_serialize_count["count"] == 1 

191 

192 # Test that normal strings still work (use default handler) 

193 result = serializer.json_serialize("normal string") 

194 assert result == "normal string" 

195 

196 

197def test_custom_handler_execution_order(): 

198 """Test that handlers_pre are executed before default handlers.""" 

199 executed_handlers = [] 

200 

201 def tracking_check(handler_name): 

202 def check(self, obj, path): 

203 executed_handlers.append(handler_name) 

204 return isinstance(obj, dict) and "_test_marker" in obj 

205 

206 return check 

207 

208 def tracking_serialize(handler_name): 

209 def serialize(self, obj, path): 

210 return {"handled_by": handler_name} 

211 

212 return serialize 

213 

214 handler1 = SerializerHandler( 

215 check=tracking_check("handler1"), 

216 serialize_func=tracking_serialize("handler1"), 

217 uid="handler1", 

218 desc="First custom handler", 

219 ) 

220 

221 handler2 = SerializerHandler( 

222 check=tracking_check("handler2"), 

223 serialize_func=tracking_serialize("handler2"), 

224 uid="handler2", 

225 desc="Second custom handler", 

226 ) 

227 

228 serializer = JsonSerializer(handlers_pre=(handler1, handler2)) 

229 

230 test_obj = {"_test_marker": True} 

231 result = serializer.json_serialize(test_obj) 

232 

233 # First handler that matches should be used (handler1) 

234 assert result == {"handled_by": "handler1"} 

235 assert executed_handlers[0] == "handler1" 

236 

237 

238# ============================================================================ 

239# Tests for DEFAULT_HANDLERS 

240# ============================================================================ 

241 

242 

243def test_DEFAULT_HANDLERS(): 

244 """Test that all default type handlers serialize correctly.""" 

245 serializer = JsonSerializer() 

246 

247 # Test dataclass 

248 dc = SimpleDataclass(x=1, y="test", z=False) 

249 result = serializer.json_serialize(dc) 

250 assert result == {"x": 1, "y": "test", "z": False} 

251 

252 # Test namedtuple - should serialize as dict 

253 Point = namedtuple("Point", ["x", "y"]) 

254 point = Point(10, 20) 

255 result = serializer.json_serialize(point) 

256 assert result == {"x": 10, "y": 20} 

257 assert isinstance(result, dict) 

258 

259 # Test Path 

260 path = Path("/home/user/test.txt") 

261 result = serializer.json_serialize(path) 

262 assert result == "/home/user/test.txt" 

263 assert isinstance(result, str) 

264 

265 # Test set (should become dict with _FORMAT_KEY) 

266 result = serializer.json_serialize({1, 2, 3}) 

267 assert isinstance(result, dict) 

268 assert result[_FORMAT_KEY] == "set" # ty: ignore[invalid-argument-type] 

269 assert isinstance(result["data"], list) # ty: ignore[invalid-argument-type] 

270 assert set(result["data"]) == {1, 2, 3} # ty: ignore[invalid-argument-type] 

271 

272 # Test tuple (should become list) 

273 result = serializer.json_serialize((1, 2, 3)) 

274 assert result == [1, 2, 3] 

275 assert isinstance(result, list) 

276 

277 

278def test_BASE_HANDLERS(): 

279 """Test that BASE_HANDLERS work correctly (primitives, dicts, lists, tuples).""" 

280 serializer = JsonSerializer(handlers_default=BASE_HANDLERS) 

281 

282 # Base handlers should handle primitives 

283 assert serializer.json_serialize(42) == 42 

284 assert serializer.json_serialize("test") == "test" 

285 assert serializer.json_serialize(True) is True 

286 assert serializer.json_serialize(None) is None 

287 

288 # Base handlers should handle dicts and lists 

289 assert serializer.json_serialize([1, 2, 3]) == [1, 2, 3] 

290 assert serializer.json_serialize({"a": 1}) == {"a": 1} 

291 assert serializer.json_serialize((1, 2)) == [1, 2] 

292 

293 

294def test_fallback_handler(): 

295 """Test that the fallback handler works for unserializable objects.""" 

296 serializer = JsonSerializer() 

297 

298 obj = UnserializableClass() 

299 result = serializer.json_serialize(obj) 

300 

301 # Fallback handler should return dict with special keys 

302 assert isinstance(result, dict) 

303 assert "__name__" in result 

304 assert "__module__" in result 

305 assert "type" in result 

306 assert "repr" in result 

307 

308 

309# ============================================================================ 

310# Tests for nested structures 

311# ============================================================================ 

312 

313 

314def test_nested_structures(): 

315 """Test serialization of mixed types and nested dicts/lists.""" 

316 serializer = JsonSerializer() 

317 

318 # Nested dicts and lists 

319 nested = {"outer": {"inner": [1, 2, {"deep": "value"}]}} 

320 nested_result = serializer.json_serialize(nested) 

321 assert nested_result == {"outer": {"inner": [1, 2, {"deep": "value"}]}} 

322 

323 # List of dicts 

324 list_of_dicts = [{"a": 1}, {"b": 2}, {"c": 3}] 

325 list_result = serializer.json_serialize(list_of_dicts) 

326 assert list_result == [{"a": 1}, {"b": 2}, {"c": 3}] 

327 

328 # Dict of lists 

329 dict_of_lists = {"nums": [1, 2, 3], "strs": ["a", "b", "c"]} 

330 result = serializer.json_serialize(dict_of_lists) 

331 assert result == {"nums": [1, 2, 3], "strs": ["a", "b", "c"]} 

332 

333 

334def test_nested_dataclasses(): 

335 """Test serialization of nested dataclasses.""" 

336 serializer = JsonSerializer() 

337 

338 simple = SimpleDataclass(x=5, y="inner", z=True) 

339 nested = NestedDataclass(simple=simple, data={"key": "value"}) 

340 

341 result = serializer.json_serialize(nested) 

342 assert result == { 

343 "simple": {"x": 5, "y": "inner", "z": True}, 

344 "data": {"key": "value"}, 

345 } 

346 

347 

348def test_deeply_nested_structure(): 

349 """Test very deeply nested structures.""" 

350 serializer = JsonSerializer() 

351 

352 deep = {"l1": {"l2": {"l3": {"l4": {"l5": [1, 2, 3]}}}}} 

353 result = serializer.json_serialize(deep) 

354 assert result == {"l1": {"l2": {"l3": {"l4": {"l5": [1, 2, 3]}}}}} 

355 

356 

357def test_mixed_types_nested(): 

358 """Test nested structures with mixed types (dataclass, dict, list, primitives).""" 

359 serializer = JsonSerializer() 

360 

361 dc = SimpleDataclass(x=100, y="test", z=False) 

362 mixed = { 

363 "dataclass": dc, 

364 "list": [1, 2, dc], 

365 "nested": {"inner_dc": dc, "values": [10, 20]}, 

366 "primitive": 42, 

367 } 

368 

369 result = serializer.json_serialize(mixed) 

370 expected_dc = {"x": 100, "y": "test", "z": False} 

371 assert result == { 

372 "dataclass": expected_dc, 

373 "list": [1, 2, expected_dc], 

374 "nested": {"inner_dc": expected_dc, "values": [10, 20]}, 

375 "primitive": 42, 

376 } 

377 

378 

379# ============================================================================ 

380# Tests for ErrorMode handling 

381# ============================================================================ 

382 

383 

384def test_error_mode_except(): 

385 """Test that ErrorMode.EXCEPT raises SerializationException on errors.""" 

386 

387 # Create a handler that always raises an error 

388 def error_check(self, obj, path): 

389 return isinstance(obj, str) and obj == "ERROR" 

390 

391 def error_serialize(self, obj, path): 

392 raise ValueError("Intentional error") 

393 

394 error_handler = SerializerHandler( 

395 check=error_check, 

396 serialize_func=error_serialize, 

397 uid="error_handler", 

398 desc="Handler that raises errors", 

399 ) 

400 

401 serializer = JsonSerializer( 

402 error_mode=ErrorMode.EXCEPT, handlers_pre=(error_handler,) 

403 ) 

404 

405 with pytest.raises(SerializationException) as exc_info: 

406 serializer.json_serialize("ERROR") 

407 

408 assert "error serializing" in str(exc_info.value) 

409 assert "error_handler" in str(exc_info.value) 

410 

411 

412def test_error_mode_warn(): 

413 """Test that ErrorMode.WARN returns repr on errors and emits warnings.""" 

414 

415 # Create a handler that always raises an error 

416 def error_check(self, obj, path): 

417 return isinstance(obj, str) and obj == "ERROR" 

418 

419 def error_serialize(self, obj, path): 

420 raise ValueError("Intentional error") 

421 

422 error_handler = SerializerHandler( 

423 check=error_check, 

424 serialize_func=error_serialize, 

425 uid="error_handler", 

426 desc="Handler that raises errors", 

427 ) 

428 

429 serializer = JsonSerializer( 

430 error_mode=ErrorMode.WARN, handlers_pre=(error_handler,) 

431 ) 

432 

433 with warnings.catch_warnings(record=True) as w: 

434 warnings.simplefilter("always") 

435 result = serializer.json_serialize("ERROR") 

436 

437 # Should return repr instead of raising 

438 assert result == "'ERROR'" 

439 # Should have emitted a warning 

440 assert len(w) > 0 

441 assert "error serializing" in str(w[0].message) 

442 

443 

444def test_error_mode_ignore(): 

445 """Test that ErrorMode.IGNORE returns repr on errors without warnings.""" 

446 

447 # Create a handler that always raises an error 

448 def error_check(self, obj, path): 

449 return isinstance(obj, str) and obj == "ERROR" 

450 

451 def error_serialize(self, obj, path): 

452 raise ValueError("Intentional error") 

453 

454 error_handler = SerializerHandler( 

455 check=error_check, 

456 serialize_func=error_serialize, 

457 uid="error_handler", 

458 desc="Handler that raises errors", 

459 ) 

460 

461 serializer = JsonSerializer( 

462 error_mode=ErrorMode.IGNORE, handlers_pre=(error_handler,) 

463 ) 

464 

465 with warnings.catch_warnings(record=True) as w: 

466 warnings.simplefilter("always") 

467 result = serializer.json_serialize("ERROR") 

468 

469 # Should return repr 

470 assert result == "'ERROR'" 

471 # Should not have emitted warnings 

472 assert len(w) == 0 

473 

474 

475# ============================================================================ 

476# Tests for write_only_format 

477# ============================================================================ 

478 

479 

480def test_write_only_format(): 

481 """Test that write_only_format changes _FORMAT_KEY to __write_format__.""" 

482 

483 # Create a handler that outputs _FORMAT_KEY 

484 def format_check(self, obj, path): 

485 return isinstance(obj, str) and obj.startswith("FORMAT:") 

486 

487 def format_serialize(self, obj, path): 

488 return {_FORMAT_KEY: "custom_format", "data": obj[7:]} 

489 

490 format_handler = SerializerHandler( 

491 check=format_check, 

492 serialize_func=format_serialize, 

493 uid="format_handler", 

494 desc="Handler that uses _FORMAT_KEY", 

495 ) 

496 

497 # Without write_only_format 

498 serializer1 = JsonSerializer(handlers_pre=(format_handler,)) 

499 result1 = serializer1.json_serialize("FORMAT:test") 

500 assert isinstance(result1, dict) 

501 assert _FORMAT_KEY in result1 

502 assert result1[_FORMAT_KEY] == "custom_format" # ty: ignore[invalid-argument-type] 

503 

504 # With write_only_format 

505 serializer2 = JsonSerializer(handlers_pre=(format_handler,), write_only_format=True) 

506 result2 = serializer2.json_serialize("FORMAT:test") 

507 assert isinstance(result2, dict) 

508 assert _FORMAT_KEY not in result2 

509 assert "__write_format__" in result2 

510 assert result2["__write_format__"] == "custom_format" # ty: ignore[invalid-argument-type] 

511 assert result2["data"] == "test" # ty: ignore[invalid-argument-type] 

512 

513 

514# ============================================================================ 

515# Tests for SerializerHandler.serialize() 

516# ============================================================================ 

517 

518 

519def test_SerializerHandler_serialize(): 

520 """Test that SerializerHandler can serialize its own metadata.""" 

521 

522 def simple_check(self, obj, path): 

523 """Check if object is an integer.""" 

524 return isinstance(obj, int) 

525 

526 def simple_serialize(self, obj, path): 

527 """Serialize integer.""" 

528 return obj * 2 

529 

530 handler = SerializerHandler( 

531 check=simple_check, 

532 serialize_func=simple_serialize, 

533 uid="test_handler", 

534 desc="Test handler description", 

535 ) 

536 

537 metadata = handler.serialize() 

538 

539 assert isinstance(metadata, dict) 

540 assert "check" in metadata 

541 assert "serialize_func" in metadata 

542 assert "uid" in metadata 

543 assert "desc" in metadata 

544 

545 assert metadata["uid"] == "test_handler" 

546 assert metadata["desc"] == "Test handler description" 

547 

548 # Check that code and doc are included 

549 check_data = metadata["check"] 

550 assert isinstance(check_data, dict) 

551 assert "code" in check_data 

552 assert "doc" in check_data 

553 

554 serialize_func_data = metadata["serialize_func"] 

555 assert isinstance(serialize_func_data, dict) 

556 assert "code" in serialize_func_data 

557 assert "doc" in serialize_func_data 

558 

559 

560# ============================================================================ 

561# Tests for hashify 

562# ============================================================================ 

563 

564 

565def test_hashify(): 

566 """Test JsonSerializer.hashify() method.""" 

567 serializer = JsonSerializer() 

568 

569 # Test that it converts to hashable types 

570 result = serializer.hashify({"a": [1, 2, 3]}) 

571 assert isinstance(result, tuple) 

572 assert result == (("a", (1, 2, 3)),) 

573 # Should be hashable 

574 hash(result) 

575 

576 # Test with list 

577 result = serializer.hashify([1, 2, 3]) 

578 assert result == (1, 2, 3) 

579 hash(result) 

580 

581 # Test with primitive (already hashable) 

582 result = serializer.hashify(42) 

583 assert result == 42 

584 hash(result) 

585 

586 

587def test_hashify_force(): 

588 """Test hashify with force parameter.""" 

589 serializer = JsonSerializer() 

590 

591 # With force=True (default), should handle unhashable objects 

592 obj = UnserializableClass() 

593 result = serializer.hashify(obj, force=True) 

594 assert isinstance(result, tuple) # Converted to hashable form 

595 

596 

597# ============================================================================ 

598# Tests for path tracking 

599# ============================================================================ 

600 

601 

602def test_path_tracking(): 

603 """Test that paths are correctly tracked through nested serialization.""" 

604 paths_seen = [] 

605 

606 def tracking_check(self, obj, path): 

607 paths_seen.append(path) 

608 return False # Never actually handle, just track 

609 

610 tracking_handler = SerializerHandler( 

611 check=tracking_check, 

612 serialize_func=lambda self, obj, path: obj, 

613 uid="tracking", 

614 desc="Path tracking handler", 

615 ) 

616 

617 serializer = JsonSerializer(handlers_pre=(tracking_handler,)) 

618 

619 # Serialize nested structure 

620 nested = {"a": {"b": [1, 2]}} 

621 serializer.json_serialize(nested) 

622 

623 # Check that we saw paths for nested elements 

624 assert tuple() in paths_seen # Root 

625 assert ("a",) in paths_seen # nested dict 

626 assert ("a", "b") in paths_seen # nested list 

627 assert ("a", "b", 0) in paths_seen # first element 

628 assert ("a", "b", 1) in paths_seen # second element 

629 

630 

631# ============================================================================ 

632# Tests for initialization 

633# ============================================================================ 

634 

635 

636def test_JsonSerializer_init_no_positional_args(): 

637 """Test that JsonSerializer raises ValueError on positional arguments.""" 

638 with pytest.raises(ValueError, match="no positional arguments"): 

639 JsonSerializer("invalid", "args") # type: ignore[arg-type] 

640 

641 # Should work with keyword args 

642 serializer = JsonSerializer(error_mode=ErrorMode.WARN) 

643 assert serializer.error_mode == ErrorMode.WARN 

644 

645 

646def test_JsonSerializer_init_defaults(): 

647 """Test JsonSerializer default initialization values.""" 

648 serializer = JsonSerializer() 

649 

650 assert serializer.array_mode == "array_list_meta" 

651 assert serializer.error_mode == ErrorMode.EXCEPT 

652 assert serializer.write_only_format is False 

653 assert serializer.handlers == DEFAULT_HANDLERS 

654 

655 

656def test_JsonSerializer_init_custom_values(): 

657 """Test JsonSerializer with custom initialization values.""" 

658 custom_handler = SerializerHandler( 

659 check=lambda self, obj, path: False, 

660 serialize_func=lambda self, obj, path: obj, 

661 uid="custom", 

662 desc="Custom handler", 

663 ) 

664 

665 serializer = JsonSerializer( 

666 array_mode="list", 

667 error_mode=ErrorMode.WARN, 

668 handlers_pre=(custom_handler,), 

669 handlers_default=BASE_HANDLERS, 

670 write_only_format=True, 

671 ) 

672 

673 assert serializer.array_mode == "list" 

674 assert serializer.error_mode == ErrorMode.WARN 

675 assert serializer.write_only_format is True 

676 assert serializer.handlers[0] == custom_handler 

677 assert len(serializer.handlers) == len(BASE_HANDLERS) + 1 

678 

679 

680# ============================================================================ 

681# Edge cases and integration tests 

682# ============================================================================ 

683 

684 

685def test_empty_handlers(): 

686 """Test serializer with no handlers.""" 

687 serializer = JsonSerializer(handlers_default=tuple()) 

688 

689 # Should fail to serialize anything 

690 with pytest.raises(SerializationException): 

691 serializer.json_serialize(42) 

692 

693 

694# TODO: Implement circular reference protection in the future. see https://github.com/mivanit/muutils/issues/62 

695@pytest.mark.skip( 

696 reason="we don't currently have circular reference protection, see https://github.com/mivanit/muutils/issues/62" 

697) 

698def test_circular_reference_protection(): 

699 """Test that circular references don't cause infinite loops (will hit recursion limit).""" 

700 # Note: This test verifies the expected behavior (recursion error) rather than 

701 # infinite loop, as the module doesn't explicitly handle circular references 

702 serializer = JsonSerializer() 

703 

704 # Create circular reference 

705 circular = {"a": None} 

706 circular["a"] = circular # type: ignore 

707 

708 # Should eventually raise RecursionError 

709 with pytest.raises(RecursionError): 

710 serializer.json_serialize(circular) 

711 

712 

713def test_large_nested_structure(): 

714 """Test serialization of large nested structure.""" 

715 serializer = JsonSerializer() 

716 

717 # Create large nested list 

718 large = [[i, i * 2, i * 3] for i in range(100)] 

719 result = serializer.json_serialize(large) 

720 assert isinstance(result, list) 

721 assert len(result) == 100 

722 assert result[0] == [0, 0, 0] 

723 assert result[99] == [99, 198, 297] 

724 

725 

726def test_mixed_container_types(): 

727 """Test serialization of sets, frozensets, and other iterables.""" 

728 serializer = JsonSerializer() 

729 

730 # Set - serialized with format key 

731 result = serializer.json_serialize({3, 1, 2}) 

732 assert isinstance(result, dict) 

733 assert _FORMAT_KEY in result 

734 assert result[_FORMAT_KEY] == "set" # ty: ignore[invalid-argument-type] 

735 assert isinstance(result["data"], list) # ty: ignore[invalid-argument-type] 

736 assert set(result["data"]) == {1, 2, 3} # ty: ignore[invalid-argument-type] 

737 

738 # Frozenset - serialized with format key 

739 result = serializer.json_serialize(frozenset([4, 5, 6])) 

740 assert isinstance(result, dict) 

741 assert _FORMAT_KEY in result 

742 assert result[_FORMAT_KEY] == "frozenset" # ty: ignore[invalid-argument-type] 

743 assert isinstance(result["data"], list) # ty: ignore[invalid-argument-type] 

744 assert set(result["data"]) == {4, 5, 6} # ty: ignore[invalid-argument-type] 

745 

746 # Generator (Iterable) - serialized as list 

747 gen = (x * 2 for x in range(3)) 

748 result = serializer.json_serialize(gen) 

749 assert result == [0, 2, 4] 

750 

751 

752def test_string_keys_in_dict(): 

753 """Test that dict keys are converted to strings.""" 

754 serializer = JsonSerializer() 

755 

756 # Integer keys should be converted to strings 

757 result = serializer.json_serialize({1: "a", 2: "b", 3: "c"}) 

758 assert isinstance(result, dict) 

759 assert result == {"1": "a", "2": "b", "3": "c"} 

760 assert all(isinstance(k, str) for k in result.keys())