Keep the last in-template copy on main so history can restore it; product continues in jiangchang-ui-recorder. Co-authored-by: Cursor <cursoragent@cursor.com>
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""Stable JSON serialization helpers for v2 models."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import asdict, is_dataclass
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any, TypeVar
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
def _normalize(obj: Any) -> Any:
|
|
if obj is None or isinstance(obj, (str, int, float, bool)):
|
|
return obj
|
|
if isinstance(obj, datetime):
|
|
return obj.isoformat()
|
|
if isinstance(obj, Path):
|
|
return str(obj).replace("\\", "/")
|
|
if is_dataclass(obj) and not isinstance(obj, type):
|
|
return {k: _normalize(v) for k, v in asdict(obj).items()}
|
|
if isinstance(obj, dict):
|
|
return {str(k): _normalize(v) for k, v in obj.items()}
|
|
if isinstance(obj, (list, tuple)):
|
|
return [_normalize(x) for x in obj]
|
|
return str(obj)
|
|
|
|
|
|
def to_dict(obj: Any) -> dict[str, Any]:
|
|
data = _normalize(obj)
|
|
if not isinstance(data, dict):
|
|
raise TypeError(f"expected dataclass/dict, got {type(obj)!r}")
|
|
return data
|
|
|
|
|
|
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
"""Best-effort dataclass reconstruction (ignores unknown keys)."""
|
|
if not is_dataclass(cls):
|
|
raise TypeError(f"{cls!r} is not a dataclass")
|
|
import dataclasses
|
|
|
|
fields = {f.name for f in dataclasses.fields(cls)}
|
|
kwargs = {k: v for k, v in (data or {}).items() if k in fields}
|
|
return cls(**kwargs) # type: ignore[call-arg]
|
|
|
|
|
|
def dumps_json(obj: Any, *, indent: int = 2) -> str:
|
|
return json.dumps(
|
|
to_dict(obj) if is_dataclass(obj) and not isinstance(obj, type) else _normalize(obj),
|
|
ensure_ascii=False,
|
|
indent=indent,
|
|
sort_keys=False,
|
|
) + "\n"
|
|
|
|
|
|
def write_model_json(path: Path, obj: Any) -> None:
|
|
path = Path(path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(dumps_json(obj), encoding="utf-8")
|
|
|
|
|
|
def read_json(path: Path) -> Any:
|
|
return json.loads(Path(path).read_text(encoding="utf-8"))
|