递归遍历+路径累积可精准定位差异点,关键在于传入当前路径列表、按类型分治处理、为list中dict提供id字段映射对齐、浮点数启用tolerance与NaN特殊判断,路径统一用list便于后续消费。
直接递归比对是最可控的方式,关键在于每层调用时把当前路径(如 ["user", "profile", "age"])作为参数传下去。遇到类型不一致、键缺失或值不同就立即记录路径,不继续深入。这样能精准定位到第一个差异点,也避免构建完整 diff 结构的开销。
注意:路径用 list 而不是 string 累积,方便后续拼接或转 dict.get() 形式;递归出口要覆盖 None、基本类型(str/int/bool)、list、dict 四类常见情况。
dict 类型才继续递归,其他类型直接比较"missing_in_b"
"missing_in_a"
"value_mismatch"
嵌套 dict 中常含 list(如 {"items": [{"id": 1}, {"id": 2}]}),若简单按索引比对,[{"id": 2}, {"id": 1}] 会被判为全量不一致。实际中更合理的做法是:仅当 list 元素是基本类型(str/int等)时才逐索引比;若元素是 dict,优先按某个唯一字段(如 "id")做映射匹配,再比对子项。
这需要提前约定“可识别字段”,例如传入 id_fields={"items": "id"}。没有

"list_length_mismatch" 并终止该分支{id_value: dict} 映射后比对[0]、[1] 等两个 dict 若含浮点数,3.14 == 3.1400000000000001 会返回 False,但业务上可能认为相等。同理,float("nan") == float("nan") 恒为 False,需单独判断。
建议提供 tolerance=1e-9 参数控制数值容差,并在进入比对前用 math.isclose() 替代 ==;对 NaN 统一用 math.isnan() 判断是否均为 NaN。
float 和 int 类型启用 tolerance 比较NaN,是则视为相等"3.14")自动转 float 比较,保持原始类型语义返回的差异路径必须是 list(如 ["data", "users", 0, "name"]),而不是拼好的字符串(如 "data.users[0].name")。前者可直接用于 functools.reduce(dict.get, path, target) 定位值,也便于前端渲染成树形结构或生成 patch 操作。
若需输出可读字符串,应由调用方决定格式(".".join(map(str, path)) 或带括号形式),底层函数不耦合展示逻辑。
"user-id" 不转下划线)int,不用 str(0 而非 "0")