From 597fe7fd0787825b199b51716a5143b355c7421e Mon Sep 17 00:00:00 2001 From: bigcat88 Date: Sun, 6 Jul 2025 12:29:22 +0300 Subject: [PATCH] feat(nodes): add basic dictionary manipulation nodes Signed-off-by: bigcat88 --- comfy/comfy_types/node_typing.py | 1 + comfy_extras/nodes_dict.py | 125 +++++++++++++++++++++++++++++++ nodes.py | 1 + 3 files changed, 127 insertions(+) create mode 100644 comfy_extras/nodes_dict.py diff --git a/comfy/comfy_types/node_typing.py b/comfy/comfy_types/node_typing.py index 071b98332..429ba79a4 100644 --- a/comfy/comfy_types/node_typing.py +++ b/comfy/comfy_types/node_typing.py @@ -21,6 +21,7 @@ class IO(StrEnum): """ STRING = "STRING" + DICT = "DICT" IMAGE = "IMAGE" MASK = "MASK" LATENT = "LATENT" diff --git a/comfy_extras/nodes_dict.py b/comfy_extras/nodes_dict.py new file mode 100644 index 000000000..efd8735d1 --- /dev/null +++ b/comfy_extras/nodes_dict.py @@ -0,0 +1,125 @@ +import contextlib +import json +from typing import Optional + +from comfy.comfy_types.node_typing import IO + + +class DictionaryNew: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "key_1": (IO.STRING, {"default": "", "multiline": False}), + "value_1": (IO.STRING, {"default": "", "multiline": False}), + }, + "optional": { + "key_2": (IO.STRING, {"default": "", "multiline": False}), + "value_2": (IO.STRING, {"default": "", "multiline": False}), + "key_3": (IO.STRING, {"default": "", "multiline": False}), + "value_3": (IO.STRING, {"default": "", "multiline": False}), + "key_4": (IO.STRING, {"default": "", "multiline": False}), + "value_4": (IO.STRING, {"default": "", "multiline": False}), + }, + } + + RETURN_TYPES = (IO.DICT,) + FUNCTION = "execute" + CATEGORY = "utils/dict" + + @classmethod + def execute( + cls, key_1: str, value_1: str, key_2: str, value_2: str, key_3: str, value_3: str, key_4: str, value_4: str, + ): + return ( + { + k: v + for k, v in [ + (key_1, value_1), + (key_2, value_2), + (key_3, value_3), + (key_4, value_4), + ] + if k + }, + ) + + +class DictionaryConvert: + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"dictionary_text": (IO.STRING, {"forceInput": True})}, + "optional": {"fallback_dict": (IO.DICT,)}, + } + + DESCRIPTION = "Parses a string into a dictionary" + RETURN_TYPES = (IO.DICT,) + FUNCTION = "execute" + CATEGORY = "utils/dict" + + @classmethod + def execute(cls, dictionary_text: str, fallback_dict: Optional[dict] = None): + with contextlib.suppress(Exception): + return json.loads(dictionary_text), + return (fallback_dict,) if fallback_dict is not None else ({},) + + +class DictionaryGet: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "dictionary": (IO.DICT,), + "key": (IO.STRING, {"default": "", "multiline": False}), + }, + "optional": { + "default_value": (IO.STRING, {"default": "", "multiline": False}), + }, + } + + RETURN_TYPES = (IO.DICT,) + FUNCTION = "execute" + CATEGORY = "utils/dict" + + @classmethod + def execute(cls, dictionary: dict, key: str, default_value=""): + return (str(dictionary.get(key, default_value)),) + + +class DictionaryUpdate: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "dict_1": (IO.DICT,), + "dict_2": (IO.DICT,), + }, + "optional": { + "dict_3": (IO.DICT,), + "dict_4": (IO.DICT,), + }, + } + + RETURN_TYPES = (IO.DICT,) + FUNCTION = "execute" + CATEGORY = "utils/dict" + + @classmethod + def execute(cls, dict_1: dict, dict_2: dict, dict_3: Optional[dict] = None, dict_4: Optional[dict] = None): + return ({**dict_1, **dict_2, **(dict_3 or {}), **(dict_4 or {})},) + + +NODE_CLASS_MAPPINGS = { + "DictionaryNew": DictionaryNew, + "DictionaryConvert": DictionaryConvert, + "DictionaryGet": DictionaryGet, + "DictionaryUpdate": DictionaryUpdate, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DictionaryNew": "Dictionary New", + "DictionaryConvert": "Convert to Dictionary", + "DictionaryGet": "Dictionary Get", + "DictionaryUpdate": "Dictionary Update", +} diff --git a/nodes.py b/nodes.py index 1b465b9e6..af1e056f2 100644 --- a/nodes.py +++ b/nodes.py @@ -2281,6 +2281,7 @@ def init_builtin_extra_nodes(): "nodes_preview_any.py", "nodes_ace.py", "nodes_string.py", + "nodes_dict.py", "nodes_camera_trajectory.py", "nodes_edit_model.py", "nodes_tcfg.py"