feat(json): add JSON Builder and JSON Fixer nodes
JSON Builder: - Build JSON from up to 5 key-value pairs - Auto-parse values (string or nested JSON) - Category: 🚦ComfyUI_LLMs_Toolkit/JSON JSON Fixer: - Fix malformed JSON from LLM outputs - Handle: trailing commas, single quotes, markdown code blocks - Extract JSON from surrounding text - Category: 🚦ComfyUI_LLMs_Toolkit/JSON Also fixed JSON Extractor to use forceInput for upstream connection.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import json
|
||||
import re
|
||||
from typing import Tuple, Optional
|
||||
|
||||
|
||||
class JSONBuilder:
|
||||
"""
|
||||
Build JSON object from multiple key-value inputs.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"key_1": ("STRING", {"default": ""}),
|
||||
"value_1": ("STRING", {"default": "", "forceInput": True}),
|
||||
"key_2": ("STRING", {"default": ""}),
|
||||
"value_2": ("STRING", {"default": "", "forceInput": True}),
|
||||
"key_3": ("STRING", {"default": ""}),
|
||||
"value_3": ("STRING", {"default": "", "forceInput": True}),
|
||||
"key_4": ("STRING", {"default": ""}),
|
||||
"value_4": ("STRING", {"default": "", "forceInput": True}),
|
||||
"key_5": ("STRING", {"default": ""}),
|
||||
"value_5": ("STRING", {"default": "", "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("json_string",)
|
||||
FUNCTION = "build"
|
||||
CATEGORY = "🚦ComfyUI_LLMs_Toolkit/JSON"
|
||||
|
||||
def build(
|
||||
self,
|
||||
key_1: str = "", value_1: str = "",
|
||||
key_2: str = "", value_2: str = "",
|
||||
key_3: str = "", value_3: str = "",
|
||||
key_4: str = "", value_4: str = "",
|
||||
key_5: str = "", value_5: str = ""
|
||||
) -> Tuple[str]:
|
||||
"""Build JSON from key-value pairs."""
|
||||
result = {}
|
||||
|
||||
pairs = [
|
||||
(key_1, value_1), (key_2, value_2), (key_3, value_3),
|
||||
(key_4, value_4), (key_5, value_5)
|
||||
]
|
||||
|
||||
for key, value in pairs:
|
||||
if key and key.strip():
|
||||
# Try to parse value as JSON, otherwise use as string
|
||||
try:
|
||||
parsed_value = json.loads(value)
|
||||
result[key.strip()] = parsed_value
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
result[key.strip()] = value
|
||||
|
||||
json_str = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
print(f"# [🚦 LLMs_Toolkit] built JSON with {len(result)} keys")
|
||||
return (json_str,)
|
||||
|
||||
|
||||
# Register the node
|
||||
NODE_CLASS_MAPPINGS = {"JSONBuilder": JSONBuilder}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"JSONBuilder": "JSON Builder"}
|
||||
@@ -12,8 +12,7 @@ class JSONExtractor:
|
||||
return {
|
||||
"required": {
|
||||
"json_string": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "{}"
|
||||
"forceInput": True
|
||||
}),
|
||||
"key": ("STRING", {
|
||||
"default": "",
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import json
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class JSONFixer:
|
||||
"""
|
||||
Fix malformed JSON from LLM outputs.
|
||||
|
||||
Common issues fixed:
|
||||
- Trailing commas
|
||||
- Single quotes instead of double quotes
|
||||
- Unquoted keys
|
||||
- Missing quotes around string values
|
||||
- Markdown code block wrappers
|
||||
- Extra text before/after JSON
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("fixed_json",)
|
||||
FUNCTION = "fix"
|
||||
CATEGORY = "🚦ComfyUI_LLMs_Toolkit/JSON"
|
||||
|
||||
def fix(self, text: str) -> Tuple[str]:
|
||||
"""
|
||||
Attempt to fix malformed JSON.
|
||||
|
||||
Args:
|
||||
text: Raw text that should contain JSON
|
||||
|
||||
Returns:
|
||||
Fixed JSON string, or error message if unfixable
|
||||
"""
|
||||
original = text
|
||||
|
||||
# Step 1: Try direct parse first (already valid)
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
print(f"# [🚦 LLMs_Toolkit] JSON already valid")
|
||||
return (json.dumps(parsed, ensure_ascii=False),)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# Step 2: Extract JSON from markdown code blocks
|
||||
code_block_pattern = r"```(?:json)?\s*([\s\S]*?)```"
|
||||
matches = re.findall(code_block_pattern, text)
|
||||
if matches:
|
||||
text = matches[0].strip()
|
||||
|
||||
# Step 3: Find JSON-like content (starts with { or [)
|
||||
json_pattern = r'(\{[\s\S]*\}|\[[\s\S]*\])'
|
||||
json_matches = re.findall(json_pattern, text)
|
||||
if json_matches:
|
||||
# Try each match
|
||||
for match in json_matches:
|
||||
text = match.strip()
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
print(f"# [🚦 LLMs_Toolkit] extracted JSON from text")
|
||||
return (json.dumps(parsed, ensure_ascii=False),)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# Step 4: Apply common fixes
|
||||
fixed = text
|
||||
|
||||
# Remove trailing commas before } or ]
|
||||
fixed = re.sub(r',\s*([}\]])', r'\1', fixed)
|
||||
|
||||
# Replace single quotes with double quotes (careful with apostrophes)
|
||||
# Only replace quotes that look like JSON string delimiters
|
||||
fixed = re.sub(r"'([^']*)':", r'"\1":', fixed) # Keys
|
||||
fixed = re.sub(r":\s*'([^']*)'([,}\]])", r': "\1"\2', fixed) # Values
|
||||
|
||||
# Try parse after fixes
|
||||
try:
|
||||
parsed = json.loads(fixed)
|
||||
print(f"# [🚦 LLMs_Toolkit] fixed JSON (trailing commas, quotes)")
|
||||
return (json.dumps(parsed, ensure_ascii=False),)
|
||||
except json.JSONDecodeError as e:
|
||||
pass
|
||||
|
||||
# Step 5: Last resort - try to find any valid JSON substring
|
||||
for i in range(len(original)):
|
||||
if original[i] in '{[':
|
||||
for j in range(len(original), i, -1):
|
||||
try:
|
||||
candidate = original[i:j]
|
||||
parsed = json.loads(candidate)
|
||||
print(f"# [🚦 LLMs_Toolkit] extracted valid JSON substring")
|
||||
return (json.dumps(parsed, ensure_ascii=False),)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# Failed to fix
|
||||
error_msg = f"Unable to fix JSON: {str(e)}"
|
||||
print(f"# [🚦 LLMs_Toolkit] ✗ {error_msg}")
|
||||
return (error_msg,)
|
||||
|
||||
|
||||
# Register the node
|
||||
NODE_CLASS_MAPPINGS = {"JSONFixer": JSONFixer}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"JSONFixer": "JSON Fixer"}
|
||||
Reference in New Issue
Block a user