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 def _resolve_iteration_index(index, mode, total_items): if total_items <= 0: return -1 if mode == "incr": index += 1 elif mode == "decr": index -= 1 return max(0, min(index, total_items - 1)) class DumasJSONStringToObjectNode: CATEGORY = "Dumas/JSON" RETURN_TYPES = ("JSON",) FUNCTION = "convert_string_to_json" @classmethod def INPUT_TYPES(cls): return { "required": { "json_string": ("STRING", {"multiline": True}), } } def convert_string_to_json(self, json_string): try: json_object = json.loads(json_string) return (json_object,) except json.JSONDecodeError as exc: print(f"[DumasNodes] Error decoding JSON: {exc}") 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),) class DumasJSONArrayIteratorNode: CATEGORY = "Dumas/JSON" RETURN_TYPES = ("JSON", "INT", "INT") RETURN_NAMES = ("item", "current_index", "total_items") FUNCTION = "iterate" @classmethod def INPUT_TYPES(cls): return { "required": { "json_input": ("JSON",), "index": ("INT", {"default": 0, "min": 0, "step": 1}), "mode": (["fixed", "incr", "decr"], {"default": "fixed"}), } } def iterate(self, json_input, index, mode): if not isinstance(json_input, list): return (None, -1, 0) total_items = len(json_input) if total_items == 0: return (None, -1, 0) current_index = _resolve_iteration_index(index, mode, total_items) return (json_input[current_index], current_index, total_items) class DumasJSONObjectIteratorNode: CATEGORY = "Dumas/JSON" RETURN_TYPES = ("STRING", "JSON", "INT", "INT") RETURN_NAMES = ("key", "value", "current_index", "total_items") FUNCTION = "iterate" @classmethod def INPUT_TYPES(cls): return { "required": { "json_input": ("JSON",), "index": ("INT", {"default": 0, "min": 0, "step": 1}), "mode": (["fixed", "incr", "decr"], {"default": "fixed"}), } } def iterate(self, json_input, index, mode): if not isinstance(json_input, dict): return (None, None, -1, 0) items = list(json_input.items()) total_items = len(items) if total_items == 0: return (None, None, -1, 0) current_index = _resolve_iteration_index(index, mode, total_items) key, value = items[current_index] return (key, value, current_index, total_items) NODE_CLASS_MAPPINGS = { "DumasJSONStringToObject": DumasJSONStringToObjectNode, "DumasJSONObjectToString": DumasJSONObjectToStringNode, "DumasJSONGetValue": DumasJSONGetValueNode, "DumasJSONSetValue": DumasJSONSetValueNode, "DumasJSONMergeObjects": DumasJSONMergeObjectsNode, "DumasJSONKeys": DumasJSONKeysNode, "DumasJSONArrayLength": DumasJSONArrayLengthNode, "DumasJSONArrayIterator": DumasJSONArrayIteratorNode, "DumasJSONObjectIterator": DumasJSONObjectIteratorNode, } 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", "DumasJSONArrayIterator": "Dumas JSON Array Iterator", "DumasJSONObjectIterator": "Dumas JSON Object Iterator", }