如何对比多份JSON文件与固定结构JSON?含列表多实例场景
JSON结构校验优化:支持列表多实例场景
问题背景
我们需要对比多份JSON文件与固定结构模板,生成包含Expected、Actual、Result的Excel报告。现有代码在处理列表多实例场景时存在两处核心问题:
- 当列表包含多个结构一致的实例时,代码误判为Fail(原逻辑会重复提取相同键,但需求是只要实例结构匹配就标记Pass)
- 当某个列表实例出现键错误时,无法精准定位到具体实例,仅能标记整体文件为Fail
固定结构模板示例:
{ "School" : { "Teachers" : [ { "Subject" : { }, "Experience" : { }, "Date of Birth" : { } } ], "Class_Rooms" : { }, "Playgrounds" : { }, "Staff" : [ { "Year-2021" : { }, "Year-2022" : { } } ] } }
优化后完整代码
import json import os import pandas as pd from typing import List, Dict def get_structure_template(obj, path: str = "") -> Dict: """提取JSON对象的结构模板,列表仅保留第一个元素的结构作为统一标准""" if isinstance(obj, dict): return { k: get_structure_template(v, f"{path}.{k}" if path else k) for k, v in obj.items() } elif isinstance(obj, list): if not obj: return [] # 列表模板取第一个元素的结构,代表所有元素应遵循的格式 return [get_structure_template(obj[0], f"{path}[*]")] else: # 非容器类型,用类型名称作为占位符 return type(obj).__name__ def validate_structure(actual_obj, template_obj, path: str = "") -> List[Dict]: """递归校验实际对象是否符合模板,返回详细错误列表""" errors = [] if isinstance(template_obj, dict): if not isinstance(actual_obj, dict): errors.append({ "Expected Path": path, "Actual Path": path, "Expected Type": "dict", "Actual Type": type(actual_obj).__name__, "Result": "Fail" }) return errors # 检查缺失的键 missing_keys = set(template_obj.keys()) - set(actual_obj.keys()) for key in missing_keys: full_path = f"{path}.{key}" if path else key errors.append({ "Expected Value": full_path, "Actual Value": None, "Result": "Fail" }) # 检查多余的键 extra_keys = set(actual_obj.keys()) - set(template_obj.keys()) for key in extra_keys: full_path = f"{path}.{key}" if path else key errors.append({ "Expected Value": None, "Actual Value": full_path, "Result": "Fail" }) # 递归检查每个键的子结构 for key in template_obj.keys(): if key not in actual_obj: continue sub_path = f"{path}.{key}" if path else key errors.extend(validate_structure(actual_obj[key], template_obj[key], sub_path)) elif isinstance(template_obj, list): if not isinstance(actual_obj, list): errors.append({ "Expected Path": path, "Actual Path": path, "Expected Type": "list", "Actual Type": type(actual_obj).__name__, "Result": "Fail" }) return errors if not template_obj: # 模板是空列表,实际列表任意内容都允许 return errors list_item_template = template_obj[0] # 逐个校验列表中的每个实例 for idx, item in enumerate(actual_obj): sub_path = f"{path}[{idx}]" if path else f"[{idx}]" errors.extend(validate_structure(item, list_item_template, sub_path)) else: # 基础类型校验 actual_type = type(actual_obj).__name__ if actual_type != template_obj: errors.append({ "Expected Path": path, "Actual Path": path, "Expected Type": template_obj, "Actual Type": actual_type, "Result": "Fail" }) return errors def format_errors_to_rows(errors: List[Dict]) -> List[Dict]: """将错误列表转换为Excel报告的标准行格式""" rows = [] for err in errors: if "Expected Value" in err: rows.append({ "Expected Value": err["Expected Value"], "Actual Value": err["Actual Value"], "Result": err["Result"] }) else: # 类型错误的情况,补充类型信息 rows.append({ "Expected Value": f"{err['Expected Path']} ({err['Expected Type']})", "Actual Value": f"{err['Actual Path']} ({err['Actual Type']})", "Result": err["Result"] }) return rows # 配置路径(请替换为实际路径) json_folder = "Path/to/your/json/files" structure_folder = "Path/to/your/structure/file" # 加载并生成结构模板 with open(os.path.join(structure_folder, "structure.json"), "r") as f: json_structure = json.load(f) structure_template = get_structure_template(json_structure) # 初始化汇总表数据 summary_data = [] # 遍历所有JSON文件进行校验 for filename in os.listdir(json_folder): if not filename.endswith(".json"): continue file_path = os.path.join(json_folder, filename) with open(file_path, "r") as f: try: actual_data = json.load(f) except json.JSONDecodeError: summary_data.append({ "Json filename": filename, "Status": "Invalid JSON" }) continue # 执行结构校验 errors = validate_structure(actual_data, structure_template) if not errors: summary_data.append({ "Json filename": filename, "Status": "Pass" }) else: summary_data.append({ "Json filename": filename, "Status": "Fail" }) # 生成当前文件的详细错误报告 report_rows = format_errors_to_rows(errors) df = pd.DataFrame(report_rows) df.to_excel(f"{os.path.splitext(filename)[0]}_errors.xlsx", index=False) # 生成最终汇总报告 summary_df = pd.DataFrame(summary_data) with pd.ExcelWriter("Summary_report.xlsx") as writer: summary_df.to_excel(writer, sheet_name="Summary", index=False)
关键优化点
- 结构模板提取:
get_structure_template函数专门提取JSON的结构框架,列表仅保留第一个元素的结构作为统一标准,忽略实例数量,完全匹配"只要结构匹配就通过"的需求。 - 递归精准校验:
validate_structure函数递归遍历每个层级,对于列表会逐个校验每个实例的结构,错误信息会精准定位到具体的实例索引(如School.Teachers[1].suject)。 - 错误信息标准化:将类型不匹配、键缺失/多余等错误统一格式化为Excel兼容的行数据,清晰展示Expected、Actual和Result字段。
- 健壮性提升:新增JSON解析异常处理,将格式错误的文件标记为"Invalid JSON",避免程序崩溃。
内容的提问来源于stack exchange,提问作者Manish saini
相关产品推荐
相关产品推荐

