From 24d8ac5e146a51423a4bae395ca60904e866a33e Mon Sep 17 00:00:00 2001 From: Chris Dumas Date: Tue, 21 Jul 2026 10:57:07 +0000 Subject: [PATCH] Optimize Dumas JSON node hot paths --- dumas_json_nodes.py | 111 ++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/dumas_json_nodes.py b/dumas_json_nodes.py index 3f13f19..be184dd 100644 --- a/dumas_json_nodes.py +++ b/dumas_json_nodes.py @@ -1,10 +1,20 @@ -import copy +from functools import lru_cache +from itertools import islice import json +def _clone_container(value): + if isinstance(value, dict): + return dict(value) + if isinstance(value, list): + return list(value) + return value + + +@lru_cache(maxsize=256) def _parse_json_path(path): if not path.strip(): - return [] + return () segments = [] for raw_segment in path.split("."): @@ -15,7 +25,7 @@ def _parse_json_path(path): segments.append(int(segment)) else: segments.append(segment) - return segments + return tuple(segments) def _get_json_path_value(data, path): @@ -33,6 +43,56 @@ def _get_json_path_value(data, path): return current +def _build_missing_container(next_segment): + return [] if isinstance(next_segment, int) else {} + + +def _set_json_path_value_recursive(current, segments, value): + if not segments: + return value + + segment = segments[0] + is_last = len(segments) == 1 + + if isinstance(segment, int): + if not isinstance(current, list): + return None + + result = list(current) + while segment >= len(result): + result.append(None) + + if is_last: + result[segment] = value + return result + + next_current = result[segment] + if not isinstance(next_current, (dict, list)): + next_current = _build_missing_container(segments[1]) + updated = _set_json_path_value_recursive(next_current, segments[1:], value) + if updated is None: + return None + result[segment] = updated + return result + + if not isinstance(current, dict): + return None + + result = dict(current) + if is_last: + result[segment] = value + return result + + next_current = result.get(segment) + if not isinstance(next_current, (dict, list)): + next_current = _build_missing_container(segments[1]) + updated = _set_json_path_value_recursive(next_current, segments[1:], value) + if updated is None: + return None + result[segment] = updated + return result + + def _set_json_path_value(data, path, value): if not isinstance(data, dict): return None @@ -41,48 +101,20 @@ def _set_json_path_value(data, path, value): 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 + return _set_json_path_value_recursive(data, segments, value) 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) + merged = dict(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) + base_value = merged.get(key) + if isinstance(base_value, dict) and isinstance(value, dict): + merged[key] = _merge_json_objects(base_value, value) else: - merged[key] = copy.deepcopy(value) + merged[key] = _clone_container(value) return merged @@ -290,13 +322,12 @@ class DumasJSONObjectIteratorNode: if not isinstance(json_input, dict): return (None, None, -1, 0) - items = list(json_input.items()) - total_items = len(items) + total_items = len(json_input) if total_items == 0: return (None, None, -1, 0) current_index = _resolve_iteration_index(index, mode, total_items) - key, value = items[current_index] + key, value = next(islice(json_input.items(), current_index, None)) return (key, value, current_index, total_items)