Files
ComfyAssets-ComfyUI_PromptM…/utils/json_strict.py
T
Vito 966a25d129 fix(scan): ingest metadata whose graph holds NaN or Infinity (#303)
* fix(scan): ingest metadata whose graph holds NaN or Infinity

ComfyUI writes its prompt with Python's default json.dumps, so a node whose
IS_CHANGED returns float("nan") saves "is_changed": [NaN] in every PNG. The
exact-pair ingestion stored the snapshots with allow_nan=False, so the scan
failed those files ("Out of range float values are not JSON compliant: nan")
and the monitor dropped their link. The legacy pages replaced NaN with null
before JSON.parse and ingested them.

- utils/json_strict.dumps_strict: strict JSON with non-finite numbers as
  null; identical bytes for every other value.
- Pair snapshots and provenance, and the plain prompt link, store strict
  JSON (the legacy link wrote bare NaN into the database).
- The browser-parser scan fallback parses records as the legacy pages do
  (parse_page_json), so a graph with NaN still traces its prompt.
- The prompt images route answers strict JSON instead of regex-replacing
  "NaN" (which also rewrote prompt text such as "ratio: NaN"); the NaN
  cleaners also null Infinity.

Prompt text and its hash are unchanged: only numbers become null.

* fix(scan): leave the browser-parser NaN parse to the resolver parity fix

The scan fallback's legacy-page parse (parse_page_json in _graph_prompt_text)
is owned by the resolver parity change, which adds the same helper with the
legacy pages' exact rule (NaN reads as null, Infinity still rejected). This
branch keeps only the strict serialization of stored and answered JSON.
2026-10-23 06:48:21 -07:00

43 lines
1.7 KiB
Python

"""Strict JSON output for values that may carry ComfyUI's non-finite numbers.
ComfyUI writes its metadata with Python's default ``json.dumps``, so a graph can
hold ``NaN``, ``Infinity`` and ``-Infinity`` (an ``IS_CHANGED`` that returns
``float("nan")`` puts ``"is_changed": [NaN]`` into every saved prompt). Those
literals are not JSON: browsers' ``JSON.parse`` and ``allow_nan=False`` reject
them. Reading follows the legacy pages (``parse_page_json`` where the browser
parser is mirrored; Python's ``json.loads`` elsewhere). Whatever PromptManager
stores or answers as JSON goes through ``dumps_strict``, which writes each
non-finite number as ``null`` (the legacy pages' own replacement for NaN) and
leaves strings, including the words ``NaN`` or ``Infinity`` inside prompt
text, untouched.
"""
import json
from typing import Any
__all__ = ["dumps_strict"]
def _null(_constant: str) -> None:
return None
def dumps_strict(value: Any, **kwargs: Any) -> str:
"""``json.dumps(value, allow_nan=False, **kwargs)`` with non-finite numbers null.
A value without non-finite numbers serializes byte for byte as the plain
call does. Any other error (a cycle, an unserializable object) still raises.
"""
kwargs.pop("allow_nan", None)
try:
return json.dumps(value, allow_nan=False, **kwargs)
except ValueError as error:
if "Out of range float" not in str(error):
raise
# The non-strict text spells each non-finite number as a bare literal, never
# inside a string, so parsing it back maps exactly those numbers to null.
lenient = json.dumps(value, allow_nan=True, default=kwargs.get("default"))
return json.dumps(
json.loads(lenient, parse_constant=_null), allow_nan=False, **kwargs
)