简介:这是一套面向开发者与测试人员的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 jq,Ubuntu 用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 pj='jq .',这样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", type=int, default=4, help="缩进空格数") parser.add_argument("--inplace", action="store_true", help="原地覆盖原文件") parser.add_argument("--sort", action="store_true", help="按 key 排序") args = parser.parse_args() with open(args.file, "r", encoding="utf-8") as f: data = json.load(f) # 解析失败会抛 JSONDecodeError,带行列号 text = json.dumps( data, indent=args.indent, ensure_ascii=False, # 保留中文 sort_keys=args.sort ) if args.inplace: with open(args.file, "w", encoding="utf-8") as f: f.write(text) else: print(text) if __name__ == "__main__": main()逻辑说明:json.load负责解析并校验,json.dumps负责重新序列化。ensure_ascii=False是中文场景的必调参数,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[@id="userDao"]' 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_text=True) # 去掉纯空白节点 tree = etree.parse(path, parser) # pretty_print 会按缩进重排,encoding 指定输出编码 return etree.tostring(tree, pretty_print=True, encoding="utf-8", xml_declaration=True) for f in glob.glob("./configs/*.xml"): try: result = format_xml(f) with open(f, "wb") as out: out.write(result) print(f"formatted: {f}") except etree.XMLSyntaxError as e: print(f"skip {f}: {e}") # 语法错误跳过,不中断批量逻辑说明:remove_blank_text=True是关键参数,它让解析器忽略元素间的纯空白,这样pretty_print才能重新缩进而不叠加原有空白。xml_declaration=True保留<?xml version="1.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.py:XML 转 JSON 并格式化输出 import xmltodict import json with open("config.xml", "r", encoding="utf-8") as f: doc = xmltodict.parse(f.read()) # 解析成有序字典 # 转 JSON,保留中文,缩进 4 print(json.dumps(doc, ensure_ascii=False, indent=4))逻辑说明: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", encoding="utf-8") as f: a = json.load(f) with open("b.json", encoding="utf-8") as f: b = json.load(f) diff = DeepDiff( a, b, ignore_order=True, # 数组顺序不敏感 report_repetition=True, # 报告重复元素 verbose_level=2 # 输出详细路径 ) if not diff: print("两个 JSON 语义一致") else: print(json.dumps(diff, ensure_ascii=False, indent=2, default=str))逻辑说明:ignore_order=True解决数组顺序问题,这是最常调的参数。verbose_level=2会输出具体路径和旧值新值,方便定位。default=str是因为 diff 结果里可能含非序列化对象,加这个避免json parse error类的序列化报错。返回的 diff 是字典,键有values_changed、dictionary_item_added、iterable_item_removed等,按类型分类。
参数上,ignore_string_case=True忽略大小写,exclude_paths可以排除时间戳、traceId 这类每次都变的字段,接口回归测试里特别有用。math_epsilon处理浮点误差,比如0.1+0.2和0.3的比对。
4.4 把比对嵌进接口回归测试
单次比对只是开始,真正省事的是把它做成回归测试的一环:
# api_regression.py:拉取接口响应并与基线比对 import json import requests from deepdiff import DeepDiff def fetch(url): resp = requests.get(url, timeout=5) resp.raise_for_status() return resp.json() baseline = json.load(open("baseline.json", encoding="utf-8")) current = fetch("http://localhost:8080/api/order/A1001") diff = DeepDiff( baseline, current, ignore_order=True, exclude_paths=["root['timestamp']", "root['traceId']"] # 排除易变字段 ) if diff: print("回归失败,差异如下:") print(json.dumps(diff, ensure_ascii=False, indent=2, default=str)) 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_ascii=True,把非 ASCII 字符转义了。如果一边转义一边没转义,比对必然失败。
解决:所有序列化统一加ensure_ascii=False,读取时统一encoding="utf-8"。jq 默认不转义,所以 jq 和 Python 混用时尤其要注意。
5.2 数组顺序敏感导致误报
现象:接口返回的列表顺序变了,但业务上顺序无关,比对却报iterable_item_removed和iterable_item_added。
原因:默认比对是顺序敏感的,[a,b]和[b,a]被判为不同。
解决:deepdiff加ignore_order=True;jq 方案需要先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_changes=True。但这是双刃剑,类型差异有时正是 bug 根源,比如后端返回"199.5"字符串而前端期望数字,忽略类型会掩盖问题。我一般先严格比对,确认是类型噪音后再加忽略参数。
6. 进阶:把三个工具串成一条可复用的数据校验流水线
单点工具用熟了,下一步是串起来。我现在的习惯是:接口返回先落盘成 JSON,用jq快速看结构,需要比对时走deepdiff,XML 配置则用lxml批量格式化后转 JSON 统一比对。这样三个工具不是孤立的,而是一条「落盘 → 格式化 → 比对 → 报告」的流水线。
具体做法是写一个check.py,把格式化、比对、报告输出整合:
# check.py:统一入口,格式化 + 比对 + 报告 import json import sys from deepdiff import DeepDiff def load(path): with open(path, encoding="utf-8") as f: return json.load(f) def fmt(data): return json.dumps(data, ensure_ascii=False, indent=4, sort_keys=True) def compare(a_path, b_path, exclude=None): a, b = load(a_path), load(b_path) diff = DeepDiff(a, b, ignore_order=True, exclude_paths=exclude 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_ascii=False, indent=2, default=str))逻辑说明:fmt子命令做格式化,diff子命令做比对,exclude参数用逗号分隔排除路径。这个脚本可以放进 CI,每次接口变更自动跑一遍,差异直接贴到构建日志里。参数上,sort_keys=True让格式化输出稳定,方便 git 管理基线文件。
验证方法上,我一般会构造三组测试数据:完全一致、仅顺序不同、有真实字段差异,确保脚本对前两组报「一致」,第三组报出具体路径。这个自测习惯帮我避免过好几次「比对工具本身有 bug 却以为是接口问题」的乌龙。
一个具体技巧:基线文件不要手动维护,用check.py fmt从当前接口响应生成,提交到仓库,这样基线永远和实际格式一致,减少格式噪音导致的误报。XML 场景同理,先用lxml格式化再转 JSON 存基线。
踩过的坑里最深刻的一次是:早期没加exclude_paths,接口里有个serverTime字段每次不同,导致回归测试天天红,团队一度想放弃自动化比对。后来把易变字段列成排除清单,误报率直接降到接近零。这件事让我明白,比对工具的价值不在于「能比」,而在于「比得准」,而准的关键是排除噪音。希望帮到你。
本文还有配套的精品资源,点击获取