简介这是一套面向开发者与测试人员的JSON/XML处理工具集主要解决接口调试、配置文件校验及数据差异排查中的格式混乱与比对困难问题。资源包含JSON格式化工具、XML格式化工具和JSON数据比对工具三大模块格式化工具支持实时校验并显示错误行号列号提供一键格式化、复制与清空比对工具采用双面板设计左右两侧可独立输入JSON支持实时格式化、行号显示、层级折叠、差异可视化分析、数据交换及窗口最大化。压缩包共7个文件以html页面、css样式、js脚本为主另含png图标、md说明与json配置整体约37KB轻量免安装浏览器打开即可使用。目前已有243人学习下载适合需要快速定位格式错误、直观查看JSON差异的前后端开发与测试人员参考使用。1. 三个工具一条流水线为什么你每次都在重复造轮子上周帮同事排查一个接口问题对方发来一段压缩成一行的 JSON嵌套了七八层肉眼根本看不出结构。我顺手丢进一个在线格式化工具页面加载了十几秒还弹了广告。那一刻我意识到json 格式化工具、xml 格式化工具、json 数据比对工具这三样东西几乎每个后端和测试每天都在用但大多数人用的还是「随手搜一个网页版」的临时方案。问题不在于格式化本身有多难而在于当你需要离线处理、需要批量比对、需要把这一步嵌进自己的脚本或 CI 流程时网页工具就彻底不够用了。这篇笔记要讲的就是怎么用一套本地可复现的方案把 JSON 格式化、XML 格式化和 JSON 数据比对这三件事串成一条能反复用的流水线而不是每次遇到问题都去开浏览器。适合手里有接口调试、配置管理、数据迁移需求的开发和测试也适合想把这类琐事自动化的运维。2. JSON 格式化工具从命令行到脚本的三种落地方式2.1 为什么优先选命令行而不是网页工具网页版 json 格式化工具最大的问题是数据要离开你的机器。接口返回里经常带 token、用户手机号、内部订单号粘到第三方网站的那一刻数据安全就已经失控了。离线版 json 格式化工具的核心价值不是「好看」而是「数据不出本机」。命令行方案还有一个隐性好处可组合。你可以把格式化结果直接管道给 grep、diff、jq 的后续查询这是网页工具做不到的。选型上我一般分三档临时看一眼用python -m json.tool日常处理用jq需要嵌进项目脚本用 Python 的json模块自己封装。三者的关系不是替代而是场景不同。2.2 用 python -m json.tool 做最小可用格式化这是零依赖的方案只要机器上有 Python 就能跑。先准备一个压缩过的 JSON 文件# 生成一个压缩的 JSON 测试文件 echo {orderId:A1001,user:{name:zhang,phone:13800000000},items:[{sku:S1,qty:2},{sku:S2,qty:1}],total:199.5} order.json然后格式化# 标准输出格式化缩进 4 空格 python -m json.tool order.json # 输出到新文件保留中文不转义 python -m json.tool --indent 4 --no-ensure-ascii order.json order_pretty.json逻辑说明python -m json.tool把模块当脚本执行读取文件、解析、再按缩进重新序列化。参数上--indent控制缩进空格数默认是 4--no-ensure-ascii是关键不加这个参数中文会被转成\uXXXX排查中文乱码问题时特别容易翻车。--sort-keys可以按 key 排序比对两个 JSON 时先排序能减少噪音。注意这个命令对非法 JSON 会直接报错退出错误信息里带行号和列号定位语法错误比网页工具还准。但它不支持 JSON 数组作为顶层之外的流式处理超大文件几百 MB会一次性读进内存这时候要换方案。2.3 用 jq 做查询式格式化jq 是处理 json 查询函数场景的利器格式化只是它的副业。安装后macOS 用brew install jqUbuntu 用apt install jq最常用的格式化命令是# 格式化并高亮 jq . order.json # 只取 items 数组并格式化 jq .items order.json # 格式化同时按 key 排序 jq -S . order.json # 压缩回一行 jq -c . order_pretty.json逻辑说明jq .里的点表示「整个文档」jq 会重新序列化并默认缩进 2 空格。-S是 sort keys-c是 compact 压缩输出。jq 的真正价值在于.items[] | select(.qty 1)这类过滤表达式格式化只是顺带。参数上--indent n可以改缩进--tab用制表符。我一般会把 jq 设成 alias比如alias pjjq .这样cat xxx.json | pj就能快速看结构。但 jq 对超大文件和非法 JSON 的容错不如 Python遇到json parse error时它只给一个模糊位置这时候回到python -m json.tool反而更快定位。2.4 把格式化封装成可复用脚本如果团队里多人要用封装一个脚本比口头教命令靠谱#!/usr/bin/env python3 # fmt_json.py格式化 JSON 文件支持原地覆盖和中文保留 import json import sys import argparse def main(): parser argparse.ArgumentParser(description离线 JSON 格式化工具) parser.add_argument(file, help输入 JSON 文件路径) parser.add_argument(--indent, typeint, default4, help缩进空格数) parser.add_argument(--inplace, actionstore_true, help原地覆盖原文件) parser.add_argument(--sort, actionstore_true, help按 key 排序) args parser.parse_args() with open(args.file, r, encodingutf-8) as f: data json.load(f) # 解析失败会抛 JSONDecodeError带行列号 text json.dumps( data, indentargs.indent, ensure_asciiFalse, # 保留中文 sort_keysargs.sort ) if args.inplace: with open(args.file, w, encodingutf-8) as f: f.write(text) else: print(text) if __name__ __main__: main()逻辑说明json.load负责解析并校验json.dumps负责重新序列化。ensure_asciiFalse是中文场景的必调参数sort_keys在比对前统一顺序很有用。--inplace要谨慎建议先不加参数看一眼输出再覆盖。这个脚本可以直接放进项目的scripts/目录配合 pre-commit 钩子自动格式化配置文件。3. XML 格式化工具解析、高亮与批量处理3.1 XML 和 JSON 格式化的本质差异很多人以为 xml 格式化工具和 json 格式化工具是一回事其实底层逻辑差很远。JSON 是键值对结构格式化就是缩进重排XML 是标记语言格式化要处理属性、命名空间、CDATA、注释、自闭合标签还要决定属性是换行还是同行。更麻烦的是XML 里空白字符有时是有语义的比如pre内容格式化工具如果无脑缩进可能改变文档含义。所以选 XML 格式化工具时第一件事是确认它是否保留空白敏感节点。常见做法是纯展示用xmllint需要高亮和编辑用编辑器插件批量处理用 Python 的lxml。mybatis xml 高亮这个热搜词背后其实是开发在 IDE 里看 mapper 文件时希望有语法着色和结构折叠这属于编辑器能力不是格式化命令能解决的但格式化能让高亮更准确。3.2 用 xmllint 做标准格式化xmllint是 libxml2 自带的工具Linux 和 macOS 基本都有# 格式化并输出缩进 2 空格 xmllint --format --encode utf-8 config.xml # 原地格式化先备份 cp config.xml config.xml.bak xmllint --format config.xml config_fmt.xml mv config_fmt.xml config.xml # 校验 XML 是否合法不输出内容 xmllint --noout config.xml逻辑说明--format触发重新缩进--encode utf-8明确编码避免中文乱码--noout只做校验不打印。参数上--noblanks会删除空白文本节点处理配置文件时慎用可能删掉有意义的空格。--xpath可以直接查询节点比如xmllint --xpath //bean[iduserDao] config.xml。注意xmllint 对格式不规范的 XML比如属性没引号会直接报错错误信息带行号这点比很多在线工具强。但它不会自动修复遇到xml文件怎么打开和编辑这类需求它只能告诉你哪里错了编辑还得靠编辑器。3.3 用 Python lxml 做批量 XML 格式化当你有几十个 XML 文件要统一格式时命令行一个个跑太低效# batch_fmt_xml.py批量格式化目录下的 XML 文件 from lxml import etree import os import glob def format_xml(path, indent ): parser etree.XMLParser(remove_blank_textTrue) # 去掉纯空白节点 tree etree.parse(path, parser) # pretty_print 会按缩进重排encoding 指定输出编码 return etree.tostring(tree, pretty_printTrue, encodingutf-8, xml_declarationTrue) for f in glob.glob(./configs/*.xml): try: result format_xml(f) with open(f, wb) as out: out.write(result) print(fformatted: {f}) except etree.XMLSyntaxError as e: print(fskip {f}: {e}) # 语法错误跳过不中断批量逻辑说明remove_blank_textTrue是关键参数它让解析器忽略元素间的纯空白这样pretty_print才能重新缩进而不叠加原有空白。xml_declarationTrue保留?xml version1.0?声明。批量场景下用 try/except 包住单个文件一个文件语法错误不会让整个批次中断这是血泪经验——早期没加异常处理一个坏文件导致后面几十个都没处理。参数上indent在 lxml 里通过etree.tostring的pretty_print隐式控制默认 2 空格想改缩进需要自定义etree.tostring的pretty_print配合后处理或者用xml.dom.minidom的toprettyxml(indent )。minidom 更简单但性能差大文件不推荐。3.4 XML 转 JSON 的格式化衔接实际工作里经常要把 XML 配置转成 JSON 再比对这时候格式化要分两步走# xml_to_json.pyXML 转 JSON 并格式化输出 import xmltodict import json with open(config.xml, r, encodingutf-8) as f: doc xmltodict.parse(f.read()) # 解析成有序字典 # 转 JSON保留中文缩进 4 print(json.dumps(doc, ensure_asciiFalse, indent4))逻辑说明xmltodict.parse把 XML 转成 Python 字典属性默认加前缀文本内容用#text。这个转换不是无损的重复标签会变成列表单标签是字典比对时要注意这个结构差异。参数上xmltodict.parse的force_list可以强制某些标签始终为列表避免「一个元素时是字典、多个时是列表」的玄学问题。4. JSON 数据比对工具从 diff 到结构化对比4.1 为什么普通 diff 比对 JSON 会翻车直接diff a.json b.json在 JSON 上基本没用因为 key 顺序不同、缩进不同、空格不同都会报差异而实际数据可能完全一样。json 数据比对工具要解决的核心问题是「语义比对」忽略格式差异只看数据结构是否等价。热搜里 json 数据比对工具 被频繁搜索说明大量人还在用文本 diff 硬扛结果被格式噪音折磨。比对分三个层次文本层diff、结构层解析后递归比较、语义层考虑类型转换、默认值、数组顺序是否敏感。大多数场景结构层就够了少数接口测试需要语义层。4.2 用 jq 做快速结构比对轻量场景下jq 配合 diff 就能用# 先各自排序 key 并格式化再 diff diff (jq -S . a.json) (jq -S . b.json) # 只看两个 JSON 的 key 差异 diff (jq -r paths | join(.) a.json | sort) \ (jq -r paths | join(.) b.json | sort)逻辑说明jq -S .把两个文件都按 key 排序后格式化消除了顺序和缩进噪音剩下的 diff 就是真实差异。paths输出所有叶子节点的路径join(.)拼成点号路径这样能快速看出哪个字段多了或少了。参数上-r是 raw output去掉 JSON 字符串的引号。这个方案适合临时排查缺点是数组元素顺序敏感[1,2]和[2,1]会被判为不同如果业务上顺序无关需要额外处理。4.3 用 Python deepdiff 做结构化比对需要精确控制比对规则时deepdiff是首选# compare_json.py结构化比对两个 JSON import json from deepdiff import DeepDiff with open(a.json, encodingutf-8) as f: a json.load(f) with open(b.json, encodingutf-8) as f: b json.load(f) diff DeepDiff( a, b, ignore_orderTrue, # 数组顺序不敏感 report_repetitionTrue, # 报告重复元素 verbose_level2 # 输出详细路径 ) if not diff: print(两个 JSON 语义一致) else: print(json.dumps(diff, ensure_asciiFalse, indent2, defaultstr))逻辑说明ignore_orderTrue解决数组顺序问题这是最常调的参数。verbose_level2会输出具体路径和旧值新值方便定位。defaultstr是因为 diff 结果里可能含非序列化对象加这个避免json parse error类的序列化报错。返回的 diff 是字典键有values_changed、dictionary_item_added、iterable_item_removed等按类型分类。参数上ignore_string_caseTrue忽略大小写exclude_paths可以排除时间戳、traceId 这类每次都变的字段接口回归测试里特别有用。math_epsilon处理浮点误差比如0.10.2和0.3的比对。4.4 把比对嵌进接口回归测试单次比对只是开始真正省事的是把它做成回归测试的一环# api_regression.py拉取接口响应并与基线比对 import json import requests from deepdiff import DeepDiff def fetch(url): resp requests.get(url, timeout5) resp.raise_for_status() return resp.json() baseline json.load(open(baseline.json, encodingutf-8)) current fetch(http://localhost:8080/api/order/A1001) diff DeepDiff( baseline, current, ignore_orderTrue, exclude_paths[root[timestamp], root[traceId]] # 排除易变字段 ) if diff: print(回归失败差异如下) print(json.dumps(diff, ensure_asciiFalse, indent2, defaultstr)) exit(1) print(回归通过)逻辑说明exclude_paths用root[key]语法排除字段这是接口测试的后悔药——不加这个每次时间戳变化都会导致误报。exit(1)让脚本在 CI 里能正确标记失败。参数上requests的timeout必须设否则接口挂起会拖死整个测试。5. 避坑与排查格式化比对里最容易翻车的五件事5.1 中文被转成 \uXXXX 导致比对全红现象两个 JSON 内容一样比对却报大量values_changed仔细看是中文变成了\u4e2d\u6587。原因Python 的json.dumps默认ensure_asciiTrue把非 ASCII 字符转义了。如果一边转义一边没转义比对必然失败。解决所有序列化统一加ensure_asciiFalse读取时统一encodingutf-8。jq 默认不转义所以 jq 和 Python 混用时尤其要注意。5.2 数组顺序敏感导致误报现象接口返回的列表顺序变了但业务上顺序无关比对却报iterable_item_removed和iterable_item_added。原因默认比对是顺序敏感的[a,b]和[b,a]被判为不同。解决deepdiff加ignore_orderTruejq 方案需要先sort_by(.id)再比对。但要注意如果业务上顺序有意义比如排行榜就不能忽略。5.3 XML 格式化后属性顺序被打乱现象格式化 XML 后git diff显示属性顺序变了但内容没变。原因不同 XML 库对属性顺序的处理不同lxml默认保留但某些工具会按字母排序。解决格式化前先确认工具是否保留属性顺序xmllint --format保留minidom也保留。如果团队对属性顺序有规范格式化后要人工确认。这个坑在 mybatis mapper 文件里特别常见属性顺序变了 review 时很干扰。5.4 大文件一次性读入内存导致 OOM现象格式化一个 500MB 的 JSON 日志文件进程直接被系统杀掉。原因json.load和jq都是全量读入内存文件大小超过可用内存就崩。解决超大文件用流式方案Python 用ijson逐条解析或者用jq --stream流式处理。如果只是看结构先head -c 10000截取前 10KB 格式化看格式别硬扛全量。5.5 比对时忽略了类型差异现象1和1比对报差异但业务上认为相等。原因JSON 里数字和字符串是不同类型deepdiff默认严格区分。解决deepdiff加ignore_type_in_groups[(int, str)]或ignore_numeric_type_changesTrue。但这是双刃剑类型差异有时正是 bug 根源比如后端返回199.5字符串而前端期望数字忽略类型会掩盖问题。我一般先严格比对确认是类型噪音后再加忽略参数。6. 进阶把三个工具串成一条可复用的数据校验流水线单点工具用熟了下一步是串起来。我现在的习惯是接口返回先落盘成 JSON用jq快速看结构需要比对时走deepdiffXML 配置则用lxml批量格式化后转 JSON 统一比对。这样三个工具不是孤立的而是一条「落盘 → 格式化 → 比对 → 报告」的流水线。具体做法是写一个check.py把格式化、比对、报告输出整合# check.py统一入口格式化 比对 报告 import json import sys from deepdiff import DeepDiff def load(path): with open(path, encodingutf-8) as f: return json.load(f) def fmt(data): return json.dumps(data, ensure_asciiFalse, indent4, sort_keysTrue) def compare(a_path, b_path, excludeNone): a, b load(a_path), load(b_path) diff DeepDiff(a, b, ignore_orderTrue, exclude_pathsexclude or []) return diff if __name__ __main__: cmd sys.argv[1] if cmd fmt: print(fmt(load(sys.argv[2]))) elif cmd diff: exclude sys.argv[4].split(,) if len(sys.argv) 4 else [] d compare(sys.argv[2], sys.argv[3], exclude) print(一致 if not d else json.dumps(d, ensure_asciiFalse, indent2, defaultstr))逻辑说明fmt子命令做格式化diff子命令做比对exclude参数用逗号分隔排除路径。这个脚本可以放进 CI每次接口变更自动跑一遍差异直接贴到构建日志里。参数上sort_keysTrue让格式化输出稳定方便 git 管理基线文件。验证方法上我一般会构造三组测试数据完全一致、仅顺序不同、有真实字段差异确保脚本对前两组报「一致」第三组报出具体路径。这个自测习惯帮我避免过好几次「比对工具本身有 bug 却以为是接口问题」的乌龙。一个具体技巧基线文件不要手动维护用check.py fmt从当前接口响应生成提交到仓库这样基线永远和实际格式一致减少格式噪音导致的误报。XML 场景同理先用lxml格式化再转 JSON 存基线。踩过的坑里最深刻的一次是早期没加exclude_paths接口里有个serverTime字段每次不同导致回归测试天天红团队一度想放弃自动化比对。后来把易变字段列成排除清单误报率直接降到接近零。这件事让我明白比对工具的价值不在于「能比」而在于「比得准」而准的关键是排除噪音。希望帮到你。本文还有配套的精品资源点击获取