Expand Dumas JSON utility nodes

This commit is contained in:
2026-07-21 10:48:10 +00:00
parent aa0fe4fa3d
commit 3b918fd36f
3 changed files with 330 additions and 11 deletions

View File

@@ -1,12 +1,45 @@
# DumasNodes
`DumasNodes` is a ComfyUI custom-node pack for generic utility workflows under the Dumas brand. The repo starts by replicating the JSON string parsing helper from [`a-und-b/ComfyUI_JSON_Helper`](https://github.com/a-und-b/ComfyUI_JSON_Helper) and rebadging it into a reusable `Dumas` namespace.
`DumasNodes` is a ComfyUI custom-node pack for generic utility workflows under the Dumas brand.
## Included Nodes
- `Dumas JSON String to Object`
- Converts a JSON string into a JSON object so downstream nodes can consume structured data.
- Lives in the `Dumas/JSON` category.
- Input: `json_string`
- Output: parsed `JSON`
- Use it to turn raw JSON text into a structured object.
- `Dumas JSON Object to String`
- Inputs: `json_object`, `pretty`, `sort_keys`
- Output: serialized `STRING`
- Use it to compact or pretty-print JSON for prompts, logs, or file output.
- `Dumas JSON Get Value`
- Inputs: `json_object`, `path`
- Output: `JSON`
- Reads a nested value using dot paths like `user.profile.name` or `items.0.id`.
- `Dumas JSON Set Value`
- Inputs: `json_object`, `path`, `value_json`
- Output: updated `JSON`
- Writes a parsed JSON value into a nested path without mutating the original object.
- `Dumas JSON Merge Objects`
- Inputs: `base_object`, `overlay_object`
- Output: merged `JSON`
- Deep-merges dictionaries so overlay values replace or extend the base object.
- `Dumas JSON Keys`
- Input: `json_object`
- Outputs: `keys` as JSON array, `count` as integer
- Lists the top-level keys of an object and reports how many there are.
- `Dumas JSON Array Length`
- Input: `json_array`
- Output: `INT`
- Returns the length of a JSON array.
All nodes live in the `Dumas/JSON` category.
## Installation
@@ -26,16 +59,28 @@
## Usage
Add `Dumas JSON String to Object` anywhere you need to bridge plain-text JSON output into structured JSON input.
Use dot paths for nested access:
```text
user.profile.name
items.0.id
settings.render.width
```
`Dumas JSON Set Value` expects `value_json` to be valid JSON, so strings should be quoted:
```json
"Dumas"
```
```json
{"enabled": true, "retries": 3}
```
## Roadmap
This repo is intended to grow into a broader set of Dumas-branded generic utility nodes, including JSON helpers and adjacent data-manipulation tools.
## Credits
The first node is derived from the upstream `ComfyUI_JSON_Helper` project by `a-und-b`, then repackaged and branded for the Dumas node collection.
## License
MIT.

View File

@@ -1,6 +1,91 @@
import copy
import json
def _parse_json_path(path):
if not path.strip():
return []
segments = []
for raw_segment in path.split("."):
segment = raw_segment.strip()
if not segment:
continue
if segment.isdigit():
segments.append(int(segment))
else:
segments.append(segment)
return segments
def _get_json_path_value(data, path):
current = data
for segment in _parse_json_path(path):
if isinstance(segment, int):
if not isinstance(current, list) or segment >= len(current):
return None
current = current[segment]
continue
if not isinstance(current, dict) or segment not in current:
return None
current = current[segment]
return current
def _set_json_path_value(data, path, value):
if not isinstance(data, dict):
return None
segments = _parse_json_path(path)
if not segments:
return value
result = copy.deepcopy(data)
current = result
for index, segment in enumerate(segments[:-1]):
next_segment = segments[index + 1]
if isinstance(segment, int):
if not isinstance(current, list):
return None
while segment >= len(current):
current.append({} if not isinstance(next_segment, int) else [])
current = current[segment]
continue
if segment not in current or not isinstance(current[segment], (dict, list)):
current[segment] = [] if isinstance(next_segment, int) else {}
current = current[segment]
last_segment = segments[-1]
if isinstance(last_segment, int):
if not isinstance(current, list):
return None
while last_segment >= len(current):
current.append(None)
current[last_segment] = value
return result
if not isinstance(current, dict):
return None
current[last_segment] = value
return result
def _merge_json_objects(base_object, overlay_object):
if not isinstance(base_object, dict) or not isinstance(overlay_object, dict):
return None
merged = copy.deepcopy(base_object)
for key, value in overlay_object.items():
if isinstance(merged.get(key), dict) and isinstance(value, dict):
merged[key] = _merge_json_objects(merged[key], value)
else:
merged[key] = copy.deepcopy(value)
return merged
class DumasJSONStringToObjectNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("JSON",)
@@ -23,10 +108,144 @@ class DumasJSONStringToObjectNode:
return (None,)
class DumasJSONObjectToStringNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("STRING",)
FUNCTION = "convert_object_to_string"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_object": ("JSON",),
"pretty": ("BOOLEAN", {"default": True}),
"sort_keys": ("BOOLEAN", {"default": False}),
}
}
def convert_object_to_string(self, json_object, pretty, sort_keys):
indent = 2 if pretty else None
separators = None if pretty else (",", ":")
return (json.dumps(json_object, indent=indent, sort_keys=sort_keys, separators=separators),)
class DumasJSONGetValueNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("JSON",)
FUNCTION = "get_value"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_object": ("JSON",),
"path": ("STRING", {"default": "", "multiline": False}),
}
}
def get_value(self, json_object, path):
return (_get_json_path_value(json_object, path),)
class DumasJSONSetValueNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("JSON",)
FUNCTION = "set_value"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_object": ("JSON",),
"path": ("STRING", {"default": "", "multiline": False}),
"value_json": ("STRING", {"multiline": True, "default": "null"}),
}
}
def set_value(self, json_object, path, value_json):
try:
value = json.loads(value_json)
except json.JSONDecodeError as exc:
print(f"[DumasNodes] Error decoding JSON value: {exc}")
return (None,)
return (_set_json_path_value(json_object, path, value),)
class DumasJSONMergeObjectsNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("JSON",)
FUNCTION = "merge_objects"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"base_object": ("JSON",),
"overlay_object": ("JSON",),
}
}
def merge_objects(self, base_object, overlay_object):
return (_merge_json_objects(base_object, overlay_object),)
class DumasJSONKeysNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("JSON", "INT")
RETURN_NAMES = ("keys", "count")
FUNCTION = "list_keys"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_object": ("JSON",),
}
}
def list_keys(self, json_object):
if not isinstance(json_object, dict):
return (None, 0)
keys = list(json_object.keys())
return (keys, len(keys))
class DumasJSONArrayLengthNode:
CATEGORY = "Dumas/JSON"
RETURN_TYPES = ("INT",)
FUNCTION = "get_length"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_array": ("JSON",),
}
}
def get_length(self, json_array):
if not isinstance(json_array, list):
return (0,)
return (len(json_array),)
NODE_CLASS_MAPPINGS = {
"DumasJSONStringToObject": DumasJSONStringToObjectNode,
"DumasJSONObjectToString": DumasJSONObjectToStringNode,
"DumasJSONGetValue": DumasJSONGetValueNode,
"DumasJSONSetValue": DumasJSONSetValueNode,
"DumasJSONMergeObjects": DumasJSONMergeObjectsNode,
"DumasJSONKeys": DumasJSONKeysNode,
"DumasJSONArrayLength": DumasJSONArrayLengthNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DumasJSONStringToObject": "Dumas JSON String to Object",
"DumasJSONObjectToString": "Dumas JSON Object to String",
"DumasJSONGetValue": "Dumas JSON Get Value",
"DumasJSONSetValue": "Dumas JSON Set Value",
"DumasJSONMergeObjects": "Dumas JSON Merge Objects",
"DumasJSONKeys": "Dumas JSON Keys",
"DumasJSONArrayLength": "Dumas JSON Array Length",
}

View File

@@ -1,9 +1,16 @@
import unittest
from dumas_json_nodes import DumasJSONStringToObjectNode
from dumas_json_nodes import (
DumasJSONArrayLengthNode,
DumasJSONGetValueNode,
DumasJSONKeysNode,
DumasJSONMergeObjectsNode,
DumasJSONObjectToStringNode,
DumasJSONStringToObjectNode,
DumasJSONSetValueNode,
)
class DumasJSONStringToObjectNodeTests(unittest.TestCase):
class DumasJSONNodeTests(unittest.TestCase):
def test_parses_valid_json(self):
node = DumasJSONStringToObjectNode()
@@ -18,6 +25,54 @@ class DumasJSONStringToObjectNodeTests(unittest.TestCase):
self.assertEqual(result, (None,))
def test_object_to_string_can_pretty_print(self):
node = DumasJSONObjectToStringNode()
result = node.convert_object_to_string({"b": 1, "a": 2}, True, True)
self.assertEqual(result, ('{\n "a": 2,\n "b": 1\n}',))
def test_get_value_reads_nested_paths(self):
node = DumasJSONGetValueNode()
result = node.get_value({"items": [{"id": "a1"}]}, "items.0.id")
self.assertEqual(result, ("a1",))
def test_set_value_writes_nested_paths(self):
node = DumasJSONSetValueNode()
result = node.set_value({"user": {"name": "Chris"}}, "user.name", '"Dumas"')
self.assertEqual(result, ({"user": {"name": "Dumas"}},))
def test_merge_objects_deep_merges_dictionaries(self):
node = DumasJSONMergeObjectsNode()
result = node.merge_objects(
{"user": {"name": "Chris", "flags": {"admin": False}}},
{"user": {"flags": {"admin": True}, "role": "owner"}},
)
self.assertEqual(
result,
({"user": {"name": "Chris", "flags": {"admin": True}, "role": "owner"}},),
)
def test_keys_returns_key_list_and_count(self):
node = DumasJSONKeysNode()
result = node.list_keys({"name": "Dumas", "kind": "nodes"})
self.assertEqual(result, (["name", "kind"], 2))
def test_array_length_returns_length(self):
node = DumasJSONArrayLengthNode()
result = node.get_length([1, 2, 3, 4])
self.assertEqual(result, (4,))
if __name__ == "__main__":
unittest.main()