Skip to content

Commit a4b3496

Browse files
committed
feat: add sanitize_for_json function to recursively convert non-serializable types to JSON-compatible formats
1 parent 424c84d commit a4b3496

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

src/vfbquery/test_utils.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,76 @@ def safe_extract_row(result: Any, index: int = 0) -> Dict:
5353
return {}
5454
return result
5555

56+
def sanitize_for_json(obj: Any) -> Any:
57+
"""
58+
Recursively sanitize any data structure to make it JSON serializable.
59+
Converts numpy types, pandas types, and other non-serializable types to native Python types.
60+
61+
:param obj: Object to sanitize
62+
:return: JSON-serializable version of the object
63+
"""
64+
if isinstance(obj, dict):
65+
return {key: sanitize_for_json(value) for key, value in obj.items()}
66+
elif isinstance(obj, (list, tuple)):
67+
return [sanitize_for_json(item) for item in obj]
68+
elif isinstance(obj, np.integer):
69+
return int(obj)
70+
elif isinstance(obj, np.floating):
71+
return float(obj)
72+
elif isinstance(obj, np.ndarray):
73+
return obj.tolist()
74+
elif isinstance(obj, np.bool_):
75+
return bool(obj)
76+
elif hasattr(obj, 'item'): # Handle pandas scalar types
77+
return obj.item()
78+
elif isinstance(obj, pd.DataFrame):
79+
return safe_to_dict(obj)
80+
elif hasattr(obj, '__dict__'): # Handle custom objects
81+
return sanitize_for_json(obj.__dict__)
82+
else:
83+
return obj
84+
85+
def safe_json_dumps(obj: Any, **kwargs) -> str:
86+
"""
87+
Safely serialize any object to JSON string, handling numpy and pandas types.
88+
89+
:param obj: Object to serialize
90+
:param kwargs: Additional arguments to pass to json.dumps
91+
:return: JSON string
92+
"""
93+
# Set default arguments
94+
default_kwargs = {'indent': 2, 'ensure_ascii': False, 'cls': NumpyEncoder}
95+
default_kwargs.update(kwargs)
96+
97+
try:
98+
# First try with the NumpyEncoder
99+
return json.dumps(obj, **default_kwargs)
100+
except (TypeError, ValueError):
101+
# If that fails, sanitize the object first
102+
sanitized_obj = sanitize_for_json(obj)
103+
return json.dumps(sanitized_obj, **default_kwargs)
104+
105+
def pretty_print_vfb_result(result: Any, max_length: int = 1000) -> None:
106+
"""
107+
Pretty print any VFB query result in a safe, readable format.
108+
109+
:param result: Result from any VFB query function
110+
:param max_length: Maximum length of output (truncates if longer)
111+
"""
112+
try:
113+
json_str = safe_json_dumps(result)
114+
if len(json_str) > max_length:
115+
print(json_str[:max_length] + f'\n... (truncated, full length: {len(json_str)} characters)')
116+
else:
117+
print(json_str)
118+
except Exception as e:
119+
print(f'Error printing result: {e}')
120+
print(f'Result type: {type(result)}')
121+
if hasattr(result, '__dict__'):
122+
print(f'Result attributes: {list(result.__dict__.keys())}')
123+
else:
124+
print(f'Result: {str(result)[:max_length]}...')
125+
56126
def patch_vfb_connect_query_wrapper():
57127
"""
58128
Apply monkey patches to VfbConnect.neo_query_wrapper to make it handle DataFrame results safely.

0 commit comments

Comments
 (0)