Source code for homodyne.io.json_utils

"""JSON utility functions for homodyne I/O operations.

This module provides helper functions for JSON serialization of numpy arrays
and other complex objects.
"""

import math
from typing import Any

import numpy as np


def _sanitize_float(v: float) -> float | str | None:
    """Convert non-finite floats to JSON-safe representations.

    JSON spec does not support NaN, Inf, or -Inf. These are converted to
    None (NaN) or string representations (Inf/-Inf) to prevent json.dump crashes.
    """
    if math.isnan(v):
        return None
    if math.isinf(v):
        return "Infinity" if v > 0 else "-Infinity"
    return v


[docs] def json_safe(value: Any) -> Any: """Recursively convert numpy arrays and special types to JSON-safe types. Parameters ---------- value : Any Value to convert (can be nested dict, list, numpy array, etc.) Returns ------- Any JSON-serializable version of the input Examples -------- >>> json_safe(np.array([1, 2, 3])) [1, 2, 3] >>> json_safe({"arr": np.array([1.0, 2.0]), "val": np.float64(3.14)}) {'arr': [1.0, 2.0], 'val': 3.14} """ if isinstance(value, dict): return {k: json_safe(v) for k, v in value.items()} elif isinstance(value, (list, tuple)): return [json_safe(v) for v in value] elif isinstance(value, np.ndarray): # Recurse through tolist() result to sanitize any NaN/Inf floats return json_safe(value.tolist()) elif isinstance(value, (np.integer, np.floating)): v = value.item() if isinstance(v, float): return _sanitize_float(v) return v elif isinstance(value, (np.bool_,)): return bool(value) elif isinstance(value, float): return _sanitize_float(value) elif hasattr(value, "tolist"): # Recurse through json_safe so that custom array-like objects whose # tolist() returns floats containing NaN/Inf are properly sanitized. return json_safe(value.tolist()) else: return value
[docs] def json_serializer(obj: Any) -> Any: """JSON serializer for numpy arrays and other objects. Use as the `default` argument to json.dump/dumps. Parameters ---------- obj : Any Object to serialize Returns ------- Any JSON-serializable version of the object Raises ------ TypeError If object cannot be serialized (will be converted to string) Examples -------- >>> import json >>> json.dumps({"arr": np.array([1, 2, 3])}, default=json_serializer) '{"arr": [1, 2, 3]}' """ if isinstance(obj, np.ndarray): # Recurse through json_safe so NaN/Inf floats inside the array are # sanitized (tolist() converts np.nan → Python float nan, which the # stock json encoder would either reject or emit as the invalid token NaN). return json_safe(obj.tolist()) elif isinstance(obj, np.integer): return int(obj) elif isinstance(obj, np.floating): v = float(obj) return _sanitize_float(v) elif isinstance(obj, float): return _sanitize_float(obj) elif isinstance(obj, (np.bool_,)): return bool(obj) elif hasattr(obj, "tolist"): # Same sanitization for other array-like objects. return json_safe(obj.tolist()) else: return str(obj)