from pydantic import BaseModel import json from typing import Any, List, Set def check_serializable(obj: Any, path: str = "", errors: List[str] = [], visited: Set[int] = set()) -> List[str]: """ Recursively check all fields in an object for non-JSON-serializable types, avoiding infinite recursion. Skips fields in Pydantic models marked with Field(..., exclude=True). Args: obj: The object to inspect (Pydantic model, dict, list, or other). path: The current field path (e.g., 'field1.nested_field'). errors: List to collect error messages. visited: Set of object IDs to track visited objects and prevent infinite recursion. Returns: List of error messages for non-serializable fields. """ # Check for circular reference by object ID obj_id = id(obj) if obj_id in visited: errors.append(f"Field '{path}' contains a circular reference, skipping further inspection") return errors # Add current object to visited set visited.add(obj_id) try: # Handle Pydantic models if isinstance(obj, BaseModel): for field_name, field_info in obj.model_fields.items(): # Skip fields marked with exclude=True if field_info.exclude: continue value = getattr(obj, field_name) new_path = f"{path}.{field_name}" if path else field_name check_serializable(value, new_path, errors, visited) # Handle dictionaries elif isinstance(obj, dict): for key, value in obj.items(): new_path = f"{path}[{key}]" if path else str(key) check_serializable(value, new_path, errors, visited) # Handle lists, tuples, or other iterables elif isinstance(obj, (list, tuple)): for i, value in enumerate(obj): new_path = f"{path}[{i}]" if path else str(i) check_serializable(value, new_path, errors, visited) # Handle other types (check for JSON serializability) else: try: json.dumps(obj) except (TypeError, OverflowError, ValueError) as e: errors.append(f"Field '{path}' contains non-serializable type: {type(obj)} ({str(e)})") finally: # Remove the current object from visited to allow processing in other branches visited.discard(obj_id) return errors