lodum.json API documentation

# lodum.json

View Source

     1# SPDX-FileCopyrightText: 2025-present Michael R. Bernstein <zopemaven@gmail.com>
     2#
     3# SPDX-License-Identifier: Apache-2.0
     4import json
     5from typing import Any, Dict, Iterator, Type, TypeVar
     6
     7from .core import Loader, BaseDumper, BaseLoader
     8from .exception import DeserializationError
     9from .internal import dump, load, generate_schema, DEFAULT_MAX_SIZE
    10
    11T = TypeVar("T")
    12
    13# --- Public API ---
    14
    15
    16def dumps(obj: Any) -> str:
    17    """
    18    Encodes a Python object to a JSON string.
    19
    20    Args:
    21        obj: The object to encode. Must be lodum-enabled or a supported type.
    22
    23    Returns:
    24        A JSON string representation of the object.
    25    """
    26    dumper = JsonDumper()
    27    dumped_data = dump(obj, dumper)
    28    return json.dumps(dumped_data)
    29
    30
    31def loads(cls: Type[T], json_string: str, max_size: int = DEFAULT_MAX_SIZE) -> T:
    32    """
    33    Decodes a JSON string into a Python object of the specified type.
    34
    35    Args:
    36        cls: The class to instantiate.
    37        json_string: The JSON data to decode.
    38        max_size: Maximum allowed size of the input string in bytes.
    39
    40    Returns:
    41        An instance of cls populated with the decoded data.
    42
    43    Raises:
    44        DeserializationError: If the input is invalid or exceeds max_size.
    45    """
    46    if len(json_string) > max_size:
    47        raise DeserializationError(
    48            f"Input size ({len(json_string)}) exceeds maximum allowed ({max_size})"
    49        )
    50    data = json.loads(json_string)
    51    loader = JsonLoader(data)
    52    return load(cls, loader)
    53
    54
    55def schema(cls: Type[Any]) -> Dict[str, Any]:
    56    """Generates a JSON Schema for a given lodum-enabled class."""
    57    return generate_schema(cls)
    58
    59
    60# --- JSON Dumper Implementation ---
    61
    62
    63class JsonDumper(BaseDumper):
    64    def dump_bytes(self, value: bytes) -> Any:
    65        import base64
    66
    67        return base64.b64encode(value).decode("ascii")
    68
    69
    70# --- JSON Loader Implementation ---
    71
    72
    73class JsonLoader(BaseLoader):
    74    def load_list(self) -> Iterator["Loader"]:
    75        if not isinstance(self._data, list):
    76            raise DeserializationError(
    77                f"Expected list, got {type(self._data).__name__}"
    78            )
    79        return (JsonLoader(item) for item in self._data)
    80
    81    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    82        if not isinstance(self._data, dict):
    83            raise DeserializationError(
    84                f"Expected dict, got {type(self._data).__name__}"
    85            )
    86        return ((k, JsonLoader(v)) for k, v in self._data.items())
    87
    88    def load_bytes_value(self, value: Any) -> bytes:
    89        if not isinstance(value, str):
    90            raise DeserializationError(f"Expected str, got {type(value).__name__}")
    91        import base64
    92
    93        try:
    94            return base64.b64decode(value)
    95        except Exception as e:
    96            raise DeserializationError(f"Failed to decode base64: {e}")

def dumps(obj: Any) -> str: View Source

    17def dumps(obj: Any) -> str:
    18    """
    19    Encodes a Python object to a JSON string.
    20
    21    Args:
    22        obj: The object to encode. Must be lodum-enabled or a supported type.
    23
    24    Returns:
    25        A JSON string representation of the object.
    26    """
    27    dumper = JsonDumper()
    28    dumped_data = dump(obj, dumper)
    29    return json.dumps(dumped_data)

Encodes a Python object to a JSON string.

Args: obj: The object to encode. Must be lodum-enabled or a supported type.

Returns: A JSON string representation of the object.

def loads(cls: Type[~T], json_string: str, max_size: int = 10485760) -> ~T: View Source

    32def loads(cls: Type[T], json_string: str, max_size: int = DEFAULT_MAX_SIZE) -> T:
    33    """
    34    Decodes a JSON string into a Python object of the specified type.
    35
    36    Args:
    37        cls: The class to instantiate.
    38        json_string: The JSON data to decode.
    39        max_size: Maximum allowed size of the input string in bytes.
    40
    41    Returns:
    42        An instance of cls populated with the decoded data.
    43
    44    Raises:
    45        DeserializationError: If the input is invalid or exceeds max_size.
    46    """
    47    if len(json_string) > max_size:
    48        raise DeserializationError(
    49            f"Input size ({len(json_string)}) exceeds maximum allowed ({max_size})"
    50        )
    51    data = json.loads(json_string)
    52    loader = JsonLoader(data)
    53    return load(cls, loader)

Decodes a JSON string into a Python object of the specified type.

Args: cls: The class to instantiate. json_string: The JSON data to decode. max_size: Maximum allowed size of the input string in bytes.

Returns: An instance of cls populated with the decoded data.

Raises: DeserializationError: If the input is invalid or exceeds max_size.

def schema(cls: Type[Any]) -> Dict[str, Any]: View Source

    56def schema(cls: Type[Any]) -> Dict[str, Any]:
    57    """Generates a JSON Schema for a given lodum-enabled class."""
    58    return generate_schema(cls)

Generates a JSON Schema for a given lodum-enabled class.

class JsonDumper(lodum.core.BaseDumper): View Source

    64class JsonDumper(BaseDumper):
    65    def dump_bytes(self, value: bytes) -> Any:
    66        import base64
    67
    68        return base64.b64encode(value).decode("ascii")

Base implementation of the Dumper protocol to reduce duplication.

def dump_bytes(self, value: bytes) -> Any: View Source

    65    def dump_bytes(self, value: bytes) -> Any:
    66        import base64
    67
    68        return base64.b64encode(value).decode("ascii")

class JsonLoader(lodum.core.BaseLoader): View Source

    74class JsonLoader(BaseLoader):
    75    def load_list(self) -> Iterator["Loader"]:
    76        if not isinstance(self._data, list):
    77            raise DeserializationError(
    78                f"Expected list, got {type(self._data).__name__}"
    79            )
    80        return (JsonLoader(item) for item in self._data)
    81
    82    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    83        if not isinstance(self._data, dict):
    84            raise DeserializationError(
    85                f"Expected dict, got {type(self._data).__name__}"
    86            )
    87        return ((k, JsonLoader(v)) for k, v in self._data.items())
    88
    89    def load_bytes_value(self, value: Any) -> bytes:
    90        if not isinstance(value, str):
    91            raise DeserializationError(f"Expected str, got {type(value).__name__}")
    92        import base64
    93
    94        try:
    95            return base64.b64decode(value)
    96        except Exception as e:
    97            raise DeserializationError(f"Failed to decode base64: {e}")

Base implementation of the Loader protocol to reduce duplication.

def load_list(self) -> Iterator[lodum.core.Loader]: View Source

    75    def load_list(self) -> Iterator["Loader"]:
    76        if not isinstance(self._data, list):
    77            raise DeserializationError(
    78                f"Expected list, got {type(self._data).__name__}"
    79            )
    80        return (JsonLoader(item) for item in self._data)

def load_dict(self) -> Iterator[tuple[str, lodum.core.Loader]]: View Source

    82    def load_dict(self) -> Iterator[tuple[str, "Loader"]]:
    83        if not isinstance(self._data, dict):
    84            raise DeserializationError(
    85                f"Expected dict, got {type(self._data).__name__}"
    86            )
    87        return ((k, JsonLoader(v)) for k, v in self._data.items())

def load_bytes_value(self, value: Any) -> bytes: View Source

    89    def load_bytes_value(self, value: Any) -> bytes:
    90        if not isinstance(value, str):
    91            raise DeserializationError(f"Expected str, got {type(value).__name__}")
    92        import base64
    93
    94        try:
    95            return base64.b64decode(value)
    96        except Exception as e:
    97            raise DeserializationError(f"Failed to decode base64: {e}")