15
15
"""Handle typing imports based on system compatibility."""
16
16
17
17
import sys
18
- from typing import Any , Callable , List , Literal , Type , TypeVar , Union , get_args , get_origin
18
+ from typing import Any , Callable , List , Literal , Optional , Set , Type , TypeVar , Union , get_args , get_origin
19
19
20
20
21
21
UNION_TYPES : List [Any ] = [Union ]
33
33
_JSON_SERIALIZABLE_TYPES = (int , float , str , bool , type (None ))
34
34
35
35
36
- def is_jsonable (obj : Any ) -> bool :
36
+ def is_jsonable (obj : Any , _visited : Optional [ Set [ int ]] = None ) -> bool :
37
37
"""Check if an object is JSON serializable.
38
38
39
39
This is a weak check, as it does not check for the actual JSON serialization, but only for the types of the object.
@@ -43,19 +43,39 @@ def is_jsonable(obj: Any) -> bool:
43
43
- it is an instance of int, float, str, bool, or NoneType
44
44
- it is a list or tuple and all its items are json serializable
45
45
- it is a dict and all its keys are strings and all its values are json serializable
46
+
47
+ Uses a visited set to avoid infinite recursion on circular references. If object has already been visited, it is
48
+ considered not json serializable.
46
49
"""
50
+ # Initialize visited set to track object ids and detect circular references
51
+ if _visited is None :
52
+ _visited = set ()
53
+
54
+ # Detect circular reference
55
+ obj_id = id (obj )
56
+ if obj_id in _visited :
57
+ return False
58
+
59
+ # Add current object to visited before recursive checks
60
+ _visited .add (obj_id )
47
61
try :
48
62
if isinstance (obj , _JSON_SERIALIZABLE_TYPES ):
49
63
return True
50
64
if isinstance (obj , (list , tuple )):
51
- return all (is_jsonable (item ) for item in obj )
65
+ return all (is_jsonable (item , _visited ) for item in obj )
52
66
if isinstance (obj , dict ):
53
- return all (isinstance (key , _JSON_SERIALIZABLE_TYPES ) and is_jsonable (value ) for key , value in obj .items ())
67
+ return all (
68
+ isinstance (key , _JSON_SERIALIZABLE_TYPES ) and is_jsonable (value , _visited )
69
+ for key , value in obj .items ()
70
+ )
54
71
if hasattr (obj , "__json__" ):
55
72
return True
56
73
return False
57
74
except RecursionError :
58
75
return False
76
+ finally :
77
+ # Remove the object id from visited to avoid side‑effects for other branches
78
+ _visited .discard (obj_id )
59
79
60
80
61
81
def is_simple_optional_type (type_ : Type ) -> bool :
0 commit comments