Source code for moneyx.serialization

"""
Module for secure serialization and deserialization of Money objects.

This module provides functions to safely convert Money objects to and from
various serialized formats (JSON, dictionaries) with appropriate validation.
"""

import json
from decimal import Decimal
from typing import Any, Dict, Optional, Type, TypeVar, cast

from moneyx.core import Money
from moneyx.exceptions import SerializationError

# Type variable for Money class
T = TypeVar("T", bound="Money")

# We need to import Money inside the functions to avoid circular imports
# The Money class will be imported only when needed


[docs] def to_dict(money: Any) -> Dict[str, str]: """ Convert a Money object to a dictionary representation. Args: money: A Money object Returns: Dictionary with amount, currency, and rounding mode Example: >>> from moneyx import Money >>> money = Money("10.50", "USD") >>> to_dict(money) {'amount': '10.50', 'currency': 'USD', 'rounding': 'HALF_UP'} """ return { "amount": str(money.amount), "currency": money.currency.code, "rounding": money.rounding_mode, }
[docs] def from_dict(cls: Type[T], data: Dict[str, Any]) -> T: """ Create a Money object from a dictionary. Args: cls: The Money class data: Dictionary containing amount, currency, and optional rounding Returns: New Money object Raises: SerializationError: If the dictionary is missing required fields or has invalid data Example: >>> from moneyx import Money >>> data = {'amount': '10.50', 'currency': 'USD', 'rounding': 'HALF_UP'} >>> from_dict(Money, data) <Money 10.50 USD> """ # Validate required fields if not isinstance(data, dict): raise SerializationError(f"Expected dictionary, got {type(data).__name__}") required_fields = ["amount", "currency"] for field in required_fields: if field not in data: raise SerializationError(f"Missing required field: {field}") # Validate field types amount_str = data["amount"] currency_code = data["currency"] if not isinstance(amount_str, (str, int, float, Decimal)): raise SerializationError( f"Amount must be numeric or string, got {type(amount_str).__name__}", ) if not isinstance(currency_code, str): raise SerializationError( f"Currency code must be string, got {type(currency_code).__name__}", ) # Get optional rounding mode rounding_mode = data.get("rounding", "HALF_UP") if not isinstance(rounding_mode, str): raise SerializationError( f"Rounding mode must be string, got {type(rounding_mode).__name__}", ) # Validate rounding mode valid_rounding_modes = [ "HALF_UP", "HALF_DOWN", "BANKERS", "DOWN", "UP", "CEILING", "FLOOR", "HALF_ODD", "HALF_TOWARDS_ZERO", "HALF_AWAY_FROM_ZERO", ] if rounding_mode not in valid_rounding_modes: raise SerializationError(f"Invalid rounding mode: {rounding_mode}") # Create Money object with validated data try: # Import here to avoid circular imports from moneyx.core import Money return cast( T, Money( amount=amount_str, currency=currency_code, rounding=rounding_mode, # type: ignore ), ) except Exception as e: raise SerializationError(f"Failed to create Money object: {e!s}") from e
[docs] def to_json(money: Any, indent: Optional[int] = None) -> str: """ Convert a Money object to a JSON string. Args: money: A Money object indent: Optional indentation for pretty-printing Returns: JSON string representation Example: >>> from moneyx import Money >>> money = Money("10.50", "USD") >>> to_json(money) '{"amount": "10.50", "currency": "USD", "rounding": "HALF_UP"}' """ try: return json.dumps(to_dict(money), indent=indent) except Exception as e: raise SerializationError(f"Failed to serialize to JSON: {e!s}") from e
[docs] def from_json(cls: Type[T], json_str: str) -> T: """ Create a Money object from a JSON string. Args: cls: The Money class json_str: JSON string representation Returns: New Money object Raises: SerializationError: If the JSON is invalid or missing required fields Example: >>> from moneyx import Money >>> json_str = '{"amount": "10.50", "currency": "USD", "rounding": "HALF_UP"}' >>> from_json(Money, json_str) <Money 10.50 USD> """ try: data = json.loads(json_str) return from_dict(cls, data) except json.JSONDecodeError as e: raise SerializationError(f"Invalid JSON: {e!s}") from e except Exception as e: raise SerializationError(f"Failed to deserialize from JSON: {e!s}") from e