Coverage for src/mlopus/utils/json_utils.py: 76%
38 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-07-13 14:49 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-07-13 14:49 +0000
1import json
2from dataclasses import is_dataclass, asdict
3from datetime import datetime, date
4from enum import Enum
5from pathlib import Path
6from typing import Any, Callable, Dict
8import pydantic
10from mlopus.utils import time_utils
13def fallback(obj: Any) -> Dict[str, Any] | list | str | None:
14 """json.dumps fallback for types, dataclasses, pydantic, date and datetime."""
15 match obj:
16 case Path():
17 return str(obj)
18 case set():
19 return sorted(obj)
20 case type():
21 return f"{obj.__module__}.{obj.__class__}"
22 case pydantic.BaseModel():
23 return obj.dict()
24 case _ if is_dataclass(obj):
25 return asdict(obj)
26 case datetime():
27 # Get UTC timestamp in full precision ISO format with timezone info: 2024-04-01T09:00:00.000000+00:00
28 # This preserves datetime sorting when applying alphanumerical sorting to string representation
29 return time_utils.safe_repr(obj)
30 case date():
31 return obj.isoformat()
32 case Enum():
33 return obj.value
36def dumps(obj: Any, fallback_: Callable[[Any], Dict[str, Any] | str | None] = fallback, **kwargs) -> str:
37 """JSON encode with `fallback` function to handle encoding of special types."""
38 return json.dumps(obj, default=fallback_, **kwargs)
41class Decoder(json.JSONDecoder):
42 """Custom JSON decoder. Handles datetime strings in safe representation (see `time_utils.safe_repr`)."""
44 def decode(self, s, *args, **kwargs):
45 """Decode JSON value."""
46 return time_utils.maybe_parse_safe_repr(super().decode(s, *args, **kwargs))
49def loads(data: str, ignore_errors: bool = False, cls=Decoder) -> Any:
50 """JSON decode."""
51 try:
52 return json.loads(data, cls=cls)
53 except json.JSONDecodeError as error:
54 if ignore_errors:
55 return str(data)
56 raise error