# -*- coding: utf-8 -*- """JSONC(JSON with Comments)加载。 选择**自研轻量剥离**而非引入第三方依赖:本仓库运行依赖目前只有 ``httpx``, 配置加载属于启动/热更新热路径,JSONC 只需注释与尾逗号。实现按字符扫描, 在字符串字面量内保留 ``//``、``/*``、引号转义,避免误伤。 支持: - ``//`` 行注释、``/* ... */`` 块注释 - 对象/数组中的尾逗号 - UTF-8 BOM 不支持 JSON5 扩展(无引号键、单引号、十六进制、``Infinity`` 等)。 """ from __future__ import annotations import json from typing import Any def strip_jsonc(text: str) -> str: """去掉 JSONC 注释与尾逗号,返回可交给 ``json.loads`` 的文本。""" if text.startswith("\ufeff"): text = text[1:] out: list[str] = [] i = 0 n = len(text) in_string = False escape = False while i < n: ch = text[i] if in_string: out.append(ch) if escape: escape = False elif ch == "\\": escape = True elif ch == '"': in_string = False i += 1 continue if ch == '"': in_string = True out.append(ch) i += 1 continue if ch == "/" and i + 1 < n: nxt = text[i + 1] if nxt == "/": i += 2 while i < n and text[i] not in "\r\n": i += 1 continue if nxt == "*": i += 2 while i + 1 < n and not (text[i] == "*" and text[i + 1] == "/"): i += 1 i = min(i + 2, n) continue out.append(ch) i += 1 stripped = "".join(out) # 去掉尾逗号:`,}` / `,]`(可夹空白) cleaned: list[str] = [] i = 0 m = len(stripped) in_string = False escape = False while i < m: ch = stripped[i] if in_string: cleaned.append(ch) if escape: escape = False elif ch == "\\": escape = True elif ch == '"': in_string = False i += 1 continue if ch == '"': in_string = True cleaned.append(ch) i += 1 continue if ch == ",": j = i + 1 while j < m and stripped[j] in " \t\r\n": j += 1 if j < m and stripped[j] in "}]": i += 1 continue cleaned.append(ch) i += 1 return "".join(cleaned) def loads(text: str) -> Any: """解析 JSONC 文本。""" return json.loads(strip_jsonc(text)) def load(path: str | bytes, *, encoding: str = "utf-8") -> Any: """从路径读取并解析 JSONC / JSON。""" with open(path, encoding=encoding) as fh: return loads(fh.read())