37 lines
1.3 KiB
Python
37 lines
1.3 KiB
Python
"""Versioned UTF-8 JSON codec used by the serialization API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, Mapping
|
|
|
|
FORMAT_NAME = "json"
|
|
FILE_FORMAT_VERSION = 1
|
|
|
|
|
|
def load_data(path: Path) -> tuple[int, Mapping[str, Any]]:
|
|
"""Read JSON and return its file-layout version and raw root object.
|
|
|
|
JSON files without ``file_format_version`` are interpreted as version 1.
|
|
Schema conversion and migrations are intentionally handled by higher
|
|
layers.
|
|
"""
|
|
try:
|
|
with path.open("r", encoding="utf-8") as stream:
|
|
data = json.load(stream)
|
|
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
raise ValueError(f"could not read JSON document {path}: {exc}") from exc
|
|
if not isinstance(data, Mapping):
|
|
raise ValueError(f"JSON document {path} must contain an object at its root")
|
|
version = data.get("file_format_version", 1)
|
|
return version, data
|
|
|
|
|
|
def save_data(data: Mapping[str, Any], path: Path) -> None:
|
|
"""Write raw document data as indented UTF-8 JSON at the current version."""
|
|
output = {"file_format_version": FILE_FORMAT_VERSION, **data}
|
|
with path.open("w", encoding="utf-8") as stream:
|
|
json.dump(output, stream, ensure_ascii=False, indent=2)
|
|
stream.write("\n")
|