2025-06-01 23:56:11 +00:00
|
|
|
|
"""
|
|
|
|
|
JSON encoders and utilities
|
|
|
|
|
"""
|
2024-03-06 18:57:04 +00:00
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
import datetime
|
|
|
|
|
import decimal
|
|
|
|
|
from typing import Any, Union
|
2025-03-20 08:55:21 +00:00
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
import orjson
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def default_json_encoder(obj: Any) -> Any:
|
|
|
|
|
"""
|
|
|
|
|
Default JSON encoder для объектов, которые не поддерживаются стандартным JSON
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
obj: Объект для сериализации
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Сериализуемое представление объекта
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
TypeError: Если объект не может быть сериализован
|
|
|
|
|
"""
|
|
|
|
|
if hasattr(obj, "dict") and callable(obj.dict):
|
|
|
|
|
return obj.dict()
|
|
|
|
|
if hasattr(obj, "__dict__"):
|
|
|
|
|
return obj.__dict__
|
|
|
|
|
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
|
|
|
|
|
return obj.isoformat()
|
|
|
|
|
if isinstance(obj, decimal.Decimal):
|
|
|
|
|
return float(obj)
|
|
|
|
|
if hasattr(obj, "__json__"):
|
|
|
|
|
return obj.__json__()
|
|
|
|
|
msg = f"Object of type {type(obj)} is not JSON serializable"
|
|
|
|
|
raise TypeError(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def orjson_dumps(obj: Any, **kwargs: Any) -> bytes:
|
|
|
|
|
"""
|
|
|
|
|
Сериализует объект в JSON с помощью orjson
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
obj: Объект для сериализации
|
|
|
|
|
**kwargs: Дополнительные параметры для orjson.dumps
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bytes: JSON в виде байтов
|
|
|
|
|
"""
|
|
|
|
|
# Используем правильную константу для orjson
|
|
|
|
|
option_flags = orjson.OPT_SERIALIZE_DATACLASS
|
|
|
|
|
if kwargs.get("indent"):
|
|
|
|
|
option_flags |= orjson.OPT_INDENT_2
|
|
|
|
|
|
|
|
|
|
return orjson.dumps(obj, default=default_json_encoder, option=option_flags)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def orjson_loads(data: Union[str, bytes]) -> Any:
|
|
|
|
|
"""
|
|
|
|
|
Десериализует JSON с помощью orjson
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
data: JSON данные в виде строки или байтов
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Десериализованный объект
|
2025-03-20 09:52:44 +00:00
|
|
|
|
"""
|
2025-06-01 23:56:11 +00:00
|
|
|
|
return orjson.loads(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class JSONEncoder:
|
|
|
|
|
"""Кастомный JSON кодировщик на основе orjson"""
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def encode(obj: Any) -> str:
|
|
|
|
|
"""Encode object to JSON string"""
|
|
|
|
|
return orjson_dumps(obj).decode("utf-8")
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def decode(data: Union[str, bytes]) -> Any:
|
|
|
|
|
"""Decode JSON string to object"""
|
|
|
|
|
return orjson_loads(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Создаем экземпляр для обратной совместимости
|
|
|
|
|
CustomJSONEncoder = JSONEncoder()
|
|
|
|
|
|
2025-03-20 09:52:44 +00:00
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
def fast_json_dumps(obj: Any, indent: bool = False) -> str:
|
2025-03-20 09:52:44 +00:00
|
|
|
|
"""
|
2025-06-01 23:56:11 +00:00
|
|
|
|
Быстрая сериализация JSON
|
2025-03-20 09:52:44 +00:00
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
Args:
|
|
|
|
|
obj: Объект для сериализации
|
|
|
|
|
indent: Форматировать с отступами
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
JSON строка
|
|
|
|
|
"""
|
|
|
|
|
return orjson_dumps(obj, indent=indent).decode("utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fast_json_loads(data: Union[str, bytes]) -> Any:
|
|
|
|
|
"""
|
|
|
|
|
Быстрая десериализация JSON
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
data: JSON данные
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Десериализованный объект
|
|
|
|
|
"""
|
|
|
|
|
return orjson_loads(data)
|
2025-03-20 09:52:44 +00:00
|
|
|
|
|
|
|
|
|
|
2025-06-01 23:56:11 +00:00
|
|
|
|
# Экспортируем для удобства
|
|
|
|
|
dumps = fast_json_dumps
|
|
|
|
|
loads = fast_json_loads
|