如何在Python3中实现JSON树剪枝及节点增删提取操作
Python3 实现JSON树按数字索引路径剪枝
核心规则说明
路径索引仅统计值为dict(JSON对象)类型的节点,普通值类型(字符串、数字、布尔、null)不占用索引位:
- 根节点固定标记为
0 - 每一层级的dict类型子节点按出现顺序从0开始编号,路径按层级追加索引,比如根节点下第一个dict子节点路径为
0.0,第二个为0.1 - 所有操作针对dict类型节点,插入/提取/删除的单位为完整的键值对(包含key和对应的dict值)
实现代码
from copy import deepcopy def parse_path(path_str: str) -> list[int]: """解析路径字符串为整数索引列表""" parts = list(map(int, path_str.split('.'))) if not parts or parts[0] != 0: raise ValueError("路径必须以0开头,代表根节点") return parts def _get_parent_and_target(root: dict, path_parts: list[int]): """定位到目标路径的父节点,返回(父节点, 目标索引, 目标节点对应的key)""" current = root # 遍历到父节点(路径倒数第二位) for idx in path_parts[1:-1]: # 收集当前节点下所有dict类型子节点,按顺序排列 child_dicts = [(k, v) for k, v in current.items() if isinstance(v, dict)] if idx < 0 or idx >= len(child_dicts): raise IndexError(f"路径索引{idx}超出当前层级子节点范围,当前层级共有{len(child_dicts)}个对象子节点") _, current = child_dicts[idx] # 拿到目标位索引 target_idx = path_parts[-1] child_dicts = [(k, v) for k, v in current.items() if isinstance(v, dict)] if target_idx < 0 or target_idx > len(child_dicts): raise IndexError(f"目标索引{target_idx}非法,当前层级可插入/操作的索引范围为0~{len(child_dicts)}") target_key = child_dicts[target_idx][0] if target_idx < len(child_dicts) else None return current, target_idx, target_key def upsert_node(root: dict, path_str: str, node_kv: dict) -> None: """ 在指定路径插入/替换节点 :param root: 原始JSON根节点(dict类型) :param path_str: 数字索引路径,如"0.1.2" :param node_kv: 待插入的单键值对dict,如{"field2x": {"field3x": "val3x"}} """ if len(node_kv) != 1: raise ValueError("待插入内容必须是单键值对,对应一个路径节点") path_parts = parse_path(path_str) parent, target_idx, old_key = _get_parent_and_target(root, path_parts) insert_key, insert_val = next(iter(node_kv.items())) if not isinstance(insert_val, dict): raise ValueError("待插入节点的值必须是dict类型") # 如果是替换已有节点,先删除旧键 if old_key is not None: del parent[old_key] # 按位置插入新键:重新构造dict保证顺序 items = list(parent.items()) # 计算插入位置:找到第target_idx个dict子节点的位置,插在它前面 insert_pos = 0 dict_count = 0 for i, (k, v) in enumerate(items): if isinstance(v, dict): if dict_count == target_idx: insert_pos = i break dict_count += 1 else: # 插在末尾 insert_pos = len(items) items.insert(insert_pos, (insert_key, insert_val)) # 替换父节点内容 parent.clear() parent.update(items) def delete_node(root: dict, path_str: str) -> dict: """ 删除指定路径的节点,返回被删除的键值对 """ path_parts = parse_path(path_str) if len(path_parts) == 1: raise ValueError("不能删除根节点") parent, _, target_key = _get_parent_and_target(root, path_parts) deleted_val = parent.pop(target_key) return {target_key: deleted_val} def extract_node(root: dict, path_str: str) -> dict: """ 提取指定路径的子树(提取后原树会移除该节点),返回被提取的键值对 """ return delete_node(root, path_str)
使用示例
对应需求中的操作流程:
import json # 初始结构 initial_json = { "field1": "value1", "field2": { "field3": "val3", "field4": "val4" } } root = deepcopy(initial_json) print("初始结构:", json.dumps(root, ensure_ascii=False, indent=2)) # 插入0.1节点 upsert_node(root, "0.1", { "field2x": { "field3x": "val3x", "field4x": "val4x" } }) print("插入0.1后:", json.dumps(root, ensure_ascii=False, indent=2)) # 在0.1下插入0.1.0节点 upsert_node(root, "0.1.0", { "field2xx": { "field3xx": "val3xx", "field4xx": "val4xx" } }) print("插入0.1.0后:", json.dumps(root, ensure_ascii=False, indent=2)) # 提取0.1节点 extracted = extract_node(root, "0.1") print("提取的0.1节点内容:", json.dumps(extracted, ensure_ascii=False, indent=2)) print("提取后剩余结构:", json.dumps(root, ensure_ascii=False, indent=2))
实现说明
- 依赖Python3.7+版本,利用原生dict的插入顺序保证索引顺序和JSON结构顺序一致,不需要额外的线性化、树节点封装逻辑,代码可维护性高
- 所有操作直接修改传入的根节点对象,不需要指针模拟,适配Python语言特性
- 内置路径校验逻辑,非法路径会抛出明确的异常信息
- 数组类型的兼容可以按需扩展
_get_parent_and_target中的子节点收集逻辑,当前实现完全匹配医疗文件结构化场景下全对象嵌套的使用需求
内容的提问来源于stack exchange,提问作者Ursa Major
相关产品推荐
相关产品推荐

