Source code for jam.utils.json.decorators
"""JSON decorators."""
from dataclasses import dataclass, is_dataclass, field
from typing import Any, Dict, Type, TypeVar, Optional, get_type_hints
from .serde import JsonSerde
T = TypeVar("T")
[docs]
def json_field(
*,
name: Optional[str] = None,
skip_if_none: bool = False,
) -> Any:
"""Field decorator for JSON customization."""
metadata = {}
if name is not None:
metadata["json_name"] = name
if skip_if_none:
metadata["skip_if_none"] = skip_if_none
return field(metadata=metadata, default=None)
[docs]
def json_serializable(cls: Type[T]) -> Type[T]:
"""Add JSON serialization to a class."""
# If it's not a dataclass, make it one
if not is_dataclass(cls):
cls = dataclass(cls)
# Create a new class that includes JsonSerde
bases = cls.__bases__
if JsonSerde not in cls.__mro__:
bases = (JsonSerde,) + bases
namespace = {
"__module__": cls.__module__,
"__qualname__": cls.__qualname__,
"__annotations__": cls.__annotations__,
"__doc__": cls.__doc__,
}
for name, attr in cls.__dict__.items():
if name not in ("__dict__", "__weakref__"):
namespace[name] = attr
JsonSerializableClass = type(cls.__name__, bases, namespace)
JsonSerializableClass.__json_type_hints__ = get_type_hints(cls)
return JsonSerializableClass
[docs]
def json_field_metadata(
field_name: str,
*,
json_name: Optional[str] = None,
skip_if_none: bool = False,
) -> Dict[str, Any]:
"""Create metadata for a JSON field."""
metadata = {}
if json_name is not None:
metadata["json_name"] = json_name
if skip_if_none:
metadata["skip_if_none"] = skip_if_none
return metadata
[docs]
def with_json_metadata(**field_metadata: Dict[str, Any]):
"""Add JSON metadata to multiple fields."""
def decorator(cls: Type[T]) -> Type[T]:
if not is_dataclass(cls):
cls = dataclass(cls)
for field_name, metadata in field_metadata.items():
field_meta = {}
if "name" in metadata:
field_meta["json_name"] = metadata["name"]
if metadata.get("skip_if_none", False):
field_meta["skip_if_none"] = True
if field_name in cls.__dataclass_fields__:
field_obj = cls.__dataclass_fields__[field_name]
object.__setattr__(field_obj, "metadata", field_meta)
else:
raise ValueError(f"Field '{field_name}' not found in dataclass")
return cls
return decorator