"""Tolerant JSON parser for AI output — from text or from files. Copes with code fences, surrounding prose and unescaped quotes inside strings (e.g. MiniMax: "Title „p" changed"): the last `"` before the error position is escaped and parsing is retried. """ import json import logging import re from pathlib import Path log = logging.getLogger("creator.jsonio") def parse_json_text(text: str): """Parse JSON from AI output; None for input that can't be repaired.""" text = re.sub(r"^```(?:json)?\s*|\s*```$", "", (text or "").strip()) start, end = text.find("{"), text.rfind("}") if start == -1 or end <= start: return None candidate = text[start:end + 1] for _ in range(20): try: return json.loads(candidate) except json.JSONDecodeError as e: if not e.msg.startswith(("Expecting ',' delimiter", "Expecting ':' delimiter")): return None q = candidate.rfind('"', 0, e.pos) if q <= 0: return None candidate = candidate[:q] + '\\"' + candidate[q + 1:] except Exception: return None return None def read_json_file(path: Path): """Read a JSON file with the same tolerance; None if missing/invalid.""" if not path.exists(): return None try: data = parse_json_text(path.read_text(encoding="utf-8")) except Exception as e: log.debug("JSON file not readable: %s (%s)", path, e) return None if data is None: log.debug("JSON file invalid: %s", path) return data