From b46f4ba2bff5e078dc895bf7a2af31ddfdf2e960 Mon Sep 17 00:00:00 2001 From: StableLlama Date: Wed, 9 Sep 2026 10:29:14 +0200 Subject: [PATCH] add create DICT and LIST from JSON string --- CHANGELOG.md | 5 ++++ README.md | 4 +-- src/basic_data_handling/dict_nodes.py | 39 +++++++++++++++++++++++++++ src/basic_data_handling/list_nodes.py | 39 +++++++++++++++++++++++++++ tests/test_dict_nodes.py | 15 +++++++++++ tests/test_list_nodes.py | 17 ++++++++++++ 6 files changed, 117 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f28aa..84a6a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- Add "create DICT from JSON string" and "create LIST from JSON string" nodes to + parse a multiline JSON object/array into a DICT/LIST + ## [1.8.1] - 2026-09-06 - Add optional additional input for image saving to also save the prompt when provided diff --git a/README.md b/README.md index b1f82be..ecc7204 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ ComfyUI list manipulation nodes (for processing individual items): Dictionary manipulation nodes: -- **Creation**: create (generic and type-specific), create from items, create from lists, fromkeys +- **Creation**: create (generic and type-specific), create from JSON string, create from items, create from lists, fromkeys - **Access**: get, get_multiple, keys, values, items - **Modification**: set, update, setdefault, merge - **Removal**: pop, popitem, pop random, remove @@ -102,7 +102,7 @@ Integer operation nodes: Python list manipulation nodes (as a single variable): -- **Creation**: create LIST (generic and type-specific versions) +- **Creation**: create LIST (generic and type-specific versions), create LIST from JSON string - **Modification**: append, extend, insert, remove, pop, pop random, set_item, shuffle - **Access**: get_item, first, last, slice, index, contains - **Information**: length, count diff --git a/src/basic_data_handling/dict_nodes.py b/src/basic_data_handling/dict_nodes.py index 61fb9f5..e404d70 100644 --- a/src/basic_data_handling/dict_nodes.py +++ b/src/basic_data_handling/dict_nodes.py @@ -4,6 +4,7 @@ from typing import ( ) from inspect import cleandoc +import json import random try: @@ -1067,12 +1068,49 @@ class DictValues(ComfyNodeABC): return (list(input_dict.values()),) +class DictCreateFromJSONString(ComfyNodeABC): + """ + Creates a DICT from a JSON object string. + + This node takes a STRING that must contain a valid JSON object and + parses it into a DICT. The input must be a JSON object (enclosed in curly + braces); otherwise a clear error is raised. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "json_string": (IO.STRING, {"default": "{}", "widgetType": "STRING", "tooltip": "STRING containing a JSON object to parse, e.g. {\"a\": 1}."}), + } + } + + RETURN_TYPES = ("DICT",) + RETURN_NAMES = ("dict",) + OUTPUT_TOOLTIPS = ("The DICT parsed from the JSON object string.",) + CATEGORY = "Basic/DICT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_from_json" + + def create_from_json(self, json_string: str) -> tuple[dict]: + try: + result = json.loads(json_string) + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in input string: {e}") + if not isinstance(result, dict): + raise ValueError( + "The JSON string must be a JSON object (enclosed in curly braces), " + f"not {type(result).__name__}." + ) + return (result,) + + NODE_CLASS_MAPPINGS = { "Basic data handling: DictCreate": DictCreate, "Basic data handling: DictCreateFromBoolean": DictCreateFromBoolean, "Basic data handling: DictCreateFromFloat": DictCreateFromFloat, "Basic data handling: DictCreateFromInt": DictCreateFromInt, "Basic data handling: DictCreateFromString": DictCreateFromString, + "Basic data handling: DictCreateFromJSONString": DictCreateFromJSONString, "Basic data handling: DictCreateFromItemsDataList": DictCreateFromItemsDataList, "Basic data handling: DictCreateFromItemsList": DictCreateFromItemsList, "Basic data handling: DictCreateFromLists": DictCreateFromLists, @@ -1105,6 +1143,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: DictCreateFromFloat": "create DICT from FLOATs", "Basic data handling: DictCreateFromInt": "create DICT from INTs", "Basic data handling: DictCreateFromString": "create DICT from STRINGs", + "Basic data handling: DictCreateFromJSONString": "create DICT from JSON string", "Basic data handling: DictCreateFromItemsDataList": "create from items (data list)", "Basic data handling: DictCreateFromItemsList": "create from items (LIST)", "Basic data handling: DictCreateFromLists": "create from LISTs", diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index 39828f7..8d6ff02 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -1,5 +1,6 @@ from typing import Any from inspect import cleandoc +import json try: from comfy.comfy_types.node_typing import IO, ComfyNodeABC @@ -972,12 +973,49 @@ class ListToSet(ComfyNodeABC): return (set(list),) +class ListCreateFromJSONString(ComfyNodeABC): + """ + Creates a LIST from a JSON array string. + + This node takes a STRING that must contain a valid JSON array and + parses it into a LIST. The input must be a JSON array (enclosed in square + brackets); otherwise a clear error is raised. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "json_string": (IO.STRING, {"default": "[]", "widgetType": "STRING", "tooltip": "STRING containing a JSON array to parse, e.g. [1, 2, 3]."}), + } + } + + RETURN_TYPES = ("LIST",) + RETURN_NAMES = ("list",) + OUTPUT_TOOLTIPS = ("The LIST parsed from the JSON array string.",) + CATEGORY = "Basic/LIST" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_from_json" + + def create_from_json(self, json_string: str) -> tuple[list[Any]]: + try: + result = json.loads(json_string) + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in input string: {e}") + if not isinstance(result, list): + raise ValueError( + "The JSON string must be a JSON array (enclosed in square brackets), " + f"not {type(result).__name__}." + ) + return (result,) + + NODE_CLASS_MAPPINGS = { "Basic data handling: ListCreate": ListCreate, "Basic data handling: ListCreateFromBoolean": ListCreateFromBoolean, "Basic data handling: ListCreateFromFloat": ListCreateFromFloat, "Basic data handling: ListCreateFromInt": ListCreateFromInt, "Basic data handling: ListCreateFromString": ListCreateFromString, + "Basic data handling: ListCreateFromJSONString": ListCreateFromJSONString, "Basic data handling: ListAll": ListAll, "Basic data handling: ListAny": ListAny, "Basic data handling: ListAppend": ListAppend, @@ -1013,6 +1051,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: ListCreateFromFloat": "create LIST from FLOATs", "Basic data handling: ListCreateFromInt": "create LIST from INTs", "Basic data handling: ListCreateFromString": "create LIST from STRINGs", + "Basic data handling: ListCreateFromJSONString": "create LIST from JSON string", "Basic data handling: ListAll": "all", "Basic data handling: ListAny": "any", "Basic data handling: ListAppend": "append", diff --git a/tests/test_dict_nodes.py b/tests/test_dict_nodes.py index 36c0a46..0e021fe 100644 --- a/tests/test_dict_nodes.py +++ b/tests/test_dict_nodes.py @@ -11,6 +11,7 @@ from src.basic_data_handling.dict_nodes import ( DictCreateFromItemsList, DictCreateFromLists, DictCreateFromString, + DictCreateFromJSONString, DictExcludeKeys, DictFilterByKeys, DictFromKeys, @@ -112,6 +113,20 @@ def test_dict_create_from_string(): assert node.create() == ({},) +def test_dict_create_from_json_string(): + node = DictCreateFromJSONString() + # Single-line JSON object + assert node.create_from_json('{\n "key1": "value1",\n "key2": 2\n}') == ({"key1": "value1", "key2": 2},) + # Empty object + assert node.create_from_json("{}") == ({},) + # JSON array is not a valid object -> error + with pytest.raises(ValueError, match="JSON object"): + node.create_from_json("[1, 2, 3]") + # Malformed JSON -> error + with pytest.raises(ValueError): + node.create_from_json("{not json}") + + def test_dict_create_from_items_datalist(): node = DictCreateFromItemsDataList() assert node.create_from_items(item=[("key1", "value1"), ("key2", "value2")]) == (_dict_x2,) diff --git a/tests/test_list_nodes.py b/tests/test_list_nodes.py index 8c70a49..a6a50de 100644 --- a/tests/test_list_nodes.py +++ b/tests/test_list_nodes.py @@ -10,6 +10,7 @@ from src.basic_data_handling.list_nodes import ( ListCreateFromFloat, ListCreateFromInt, ListCreateFromString, + ListCreateFromJSONString, ListEnumerate, ListExtend, ListFirst, @@ -257,6 +258,22 @@ def test_list_create_from_string(): assert node.create_list() == ([],) +def test_list_create_from_json_string(): + node = ListCreateFromJSONString() + # Multiline JSON array + assert node.create_from_json('[\n 1,\n 2,\n 3\n]') == ([1, 2, 3],) + # Empty array + assert node.create_from_json("[]") == ([],) + # Mix of JSON types + assert node.create_from_json('["a", 1, true, null]') == (["a", 1, True, None],) + # JSON object is not a valid array -> error + with pytest.raises(ValueError, match="JSON array"): + node.create_from_json('{"a": 1}') + # Malformed JSON -> error + with pytest.raises(ValueError): + node.create_from_json("[1, 2,") + + def test_list_to_data_list(): node = ListToDataList() input_list = [1, 2, 3]