import importlib.util from decimal import Decimal from typing import Any, Optional # pylint: disable=no-member import orjson from fastapi import Response HAS_NUMPY = importlib.util.find_spec('numpy') is not None ORJSON_OPTS = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_NON_STR_KEYS def dumps(obj: Any, sort_keys: bool = False, separators: Optional[tuple[str, str]] = None, *, indent: bool = False) -> str: """Serializes a Python object to a JSON-encoded string. By default, this function supports serializing NumPy arrays, which Python's json module does not. Uses package `orjson` internally. """ # note that parameters `sort_keys` and `separators` are required by AsyncServer's # internal calls, which match Python's default `json.dumps` API. assert separators is None or separators == (',', ':'), \ 'NiceGUI JSON serializer only supports Python''s default ' +\ f'JSON separators "," and ":", but got {separators} instead.' opts = ORJSON_OPTS # flag for sorting by object keys if sort_keys: opts |= orjson.OPT_SORT_KEYS # flag for pretty-printing with indentation if indent: opts |= orjson.OPT_INDENT_2 return orjson.dumps(obj, option=opts, default=_orjson_converter).decode('utf-8') def loads(value: str) -> Any: """Deserialize a JSON-encoded string to a corresponding Python object/value. Uses package `orjson` internally. """ return orjson.loads(value) def _orjson_converter(obj): """Custom serializer/converter, e.g. for NumPy object arrays.""" if HAS_NUMPY: import numpy as np # pylint: disable=import-outside-toplevel if isinstance(obj, np.ndarray) and obj.dtype == np.object_: return obj.tolist() if isinstance(obj, Decimal): return float(obj) raise TypeError(f'Object of type {obj.__class__.__name__} is not JSON serializable') class NiceGUIJSONResponse(Response): """FastAPI response class to support our custom json serializer implementation. Uses package `orjson` internally. """ media_type = 'application/json' def render(self, content: Any) -> bytes: return orjson.dumps(content, option=ORJSON_OPTS, default=_orjson_converter)