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.
42 lines
1.7 KiB
Python
42 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. Python's ``json.loads`` already accepts them, so reading needs no help;
|
|
whatever PromptManager stores or answers as JSON goes through ``dumps_strict``,
|
|
which writes each non-finite number as ``null`` (the legacy pages' own
|
|
replacement) 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
|
|
)
|