Json格式化全攻略:工具、语言实现与报错排查
我最早接触 Json 格式化那会儿以为就是把一堆挤在一起的字符串“拍扁”让它能看得清楚就行。后来处理的东西多了才发现格式化这件事远不止“好看”这么简单。一个被正确格式化、并通过校验的 Json 文件等于一份自带语法检查、可读性拉满的数据契约而一个格式乱糟糟的 Json轻则让你盯半天找不出缺哪个逗号重则直接把整个服务的请求流程干挂。这篇内容我想从实际使用角度把 Json 文件格式化的方法、工具、各语言实现以及各种高频报错的排查思路理顺一遍。这篇文章适合谁看后端接口排查报错的人、前端对接数据被嵌套层级绕晕的人、用 Python 分析日志需要清洗 Json 的人、在 Qt/C# 桌面端做配置解析的人甚至只是想在浏览器阅读器里整理书源 Json、配省市区三级联动数据的人都能在里面找到能直接抄的段落。提醒一句如果你搜到的是“u盘格式化”“内存卡无法格式化”“光盘需要格式化”那咱们说的完全是两码事这里只讨论纯文本的 Json 文件不涉及任何磁盘或文件系统层面的格式化。另外文里不涉及任何代理或网络访问类内容可以放心看完直接照着操作。1. 为什么需要格式化 Json它到底改变了什么1.1 先想清楚 Json 到底长什么样JsonJavaScript Object Notation本质上就是一种基于文本的数据交换格式。它的核心结构其实就两块对象用花括号{}包裹的键值对集合和数组用方括号[]包裹的有序值列表。值可以是字符串、数字、布尔值、null、另一个对象或另一个数组。一段没有格式化的 Json 往往长这样{name:server-a,status:running,metrics:{cpu:12.5,mem:0.83},tags:[prod,primary]}这不是不能看但一旦字段多起来嵌套深起来这种“压缩形态”就非常折磨人。经过格式化之后它变成{ name: server-a, status: running, metrics: { cpu: 12.5, mem: 0.83 }, tags: [prod, primary] }这种用缩进、换行把层级关系表达出来的过程就是 Json 格式化。工具干的事情本质上只有两件先把原始文本解析成内存里的结构化数据对象再把这个对象按照缩进规则重新序列化输出。所以真正的格式化工具一定会顺带做一次语法校验——能格式化成功说明这段 Json 至少在语法上是合法的格式化报错说明文本里有语法问题比如缺引号、多逗号、括号不配对。1.2 格式化解决的三个真实问题第一个问题是排查问题。接口报错时你拿到一段被日志系统截断的响应体字符挤在一起根本看不出哪一段出了问题。格式化之后层级关系清晰哪个字段缺失、哪条字符串没闭合一眼就能锁定方向。第二个问题是数据审查和交接。我做数据对接时经常收到同事甩过来的省市区三级联动 Json还有各种书源、音乐源配置它们通常是从某处复制粘贴出来的压缩文本。直接肉眼核对嵌套层级几乎不可能先格式化再审查是最基本的动作。把字段对整齐之后只要层级和业务文档对得上数据基本不会有大问题。第三个问题是调试和演示。写技术文章、做接口文档示例、给非技术同事讲解数据结构时一段格式化好的 Json 比一段压缩文本专业得多也更容易让人理解每个字段的含义。顺带说一句服务端实际传输时并不需要格式化线上接口为了省带宽往往用压缩形态格式化只是给“人”看的机器不在乎换行和缩进。2. 选择合适的格式化工具在线、离线与编辑器插件2.1 在线工具怎么选敏感数据要注意在线 Json 格式化工具是最省事的方案比如常见的 Json.cn、BeJson 这类站点打开网页把内容粘进去一键就能得到格式化结果有的还带校验、转义、压缩等功能。对于偶尔处理一次 Json 的人来说在线工具完全够用。但这里要特别提醒一句千万别把敏感数据直接贴到在线工具里。我见过有人把线上环境的配置项、带内部信息的接口响应体直接复制到网页里格式化这等于把数据交给第三方服务。涉及密钥、token、个人信息的内容一律不要用在线工具。如果你只是格式化一段示例数据或者确认一个语法问题在线工具没问题如果是公司内部数据请使用本地工具或命令行方案。2.2 离线工具与命令行方案内网隔离环境必备很多办公环境有安全要求不能访问外网这时候离线工具就是刚需。最简单的离线方案其实不用装任何软件只要电脑上有 Python一行命令就能格式化。Python 自带的json.tool模块就是一个小巧的格式化器# 从文件读取并格式化输出 python -m json.tool input.json # 把格式化结果写回文件 python -m json.tool input.json output.json命令行工具还有一个好处就是可以放进脚本里批量处理。如果你在 Linux 服务器上工作jq是更强大的选择# 格式化并带上颜色高亮 jq . input.json # 格式化后写回文件 jq . input.json output.jsonjq不只是格式化还能做查询、过滤、重组数据是处理 Json 的瑞士军刀。Windows 环境下不想装 Python 也不想装 jq可以下载一个绿色版的“Json 格式化工具离线版”一般就是单文件 exe双击打开把内容粘进去就能用不联网也能正常工作。还有一些人用 Notepad 加 JSON Viewer 插件来实现格式化上面热词里提到的“nodepad怎么格式化json”就是这个思路装好插件后按快捷键即可。2.3 编辑器插件与快捷键日常开发首选如果你每天都要跟 Json 打交道我强烈建议直接使用编辑器的内置功能或插件不要每次都开网页。VS Code 的操作很简单打开一个.json后缀的文件右键选择“格式化文档”或者直接按Shift Alt F瞬间完成格式化。如果你希望保存时自动格式化可以在设置里打开Format On Save。这个配置本身也是写在settings.json里的改起来很方便。很多人会把“vscode 自动格式化代码在哪关闭”这个问题的答案忘了就是在这个设置项的开关上。IDEA 家族IntelliJ IDEA、PyCharm、WebStorm都有内置的 Format 功能选中 Json 内容后按Ctrl Alt L可以格式化。对于纯 Json 文件用它的内置 JSON 插件就能完成校验和格式化。如果你经常需要把 Json 转成类、或者从 Json 生成数据结构可以装一个 Json Parser 插件功能更全。这里必须纠正一个很容易踩的误区热词里有个“vscode使用clangformat格式化”很多人以为这个插件可以格式化 Json。实际上clang-format是 C/C 代码格式化工具它不负责 Json 格式化而是负责.cpp、.h这类代码文件的风格整理。VS Code 里处理 Json 用内置的格式化器就行不需要也不应该用 clang-format 去格式化数据文件。无论用什么编辑器团队协作时都应该约定统一的格式规范缩进用 2 个空格还是 4 个空格、结尾是否加换行、utf-8 编码不带 BOM。这些规则应该固化到.editorconfig或者.vscode/settings.json里提交到代码仓库让所有成员共享。否则你格式化成 4 空格、同事格式化成 2 空格每次提交都产生一大堆 diff代码评审就没法看了。3. 常用编程语言里如何格式化 Json 输出3.1 Python 环境下的格式化实操Python 是做数据清洗和接口调试最方便的语言格式化 Json 也最简单。核心方法是json.dumps()它有几个关键参数几乎每次都要用到import json data { name: order-service, env: prod, limit: 100, tags: [payment, core], extra: {timeout: 30} } # indent2 表示缩进2个空格ensure_asciiFalse 让中文原样显示 formatted json.dumps(data, ensure_asciiFalse, indent2) print(formatted)输出效果{ name: order-service, env: prod, limit: 100, tags: [ payment, core ], extra: { timeout: 30 } }注意那个ensure_asciiFalse。默认情况下 Python 会把所有非 ASCII 字符转成\uXXXX这种转义形式比如“支付”会变成\u652f\u4ed8。这在某些场景下是必须的比如为了兼容只认 ASCII 的老旧系统但绝大多数场景下我们希望在日志和配置文件里直接看到中文原文所以一定要把ensure_ascii设成False。还有一个高频需求读取一个已经存在的 Json 文件格式化以后再写回去。完整代码如下import json from pathlib import Path def format_json_file(input_path: str, output_path: str None): src Path(input_path) dst Path(output_path) if output_path else src with src.open(r, encodingutf-8) as f: # 这一步会做完整的语法校验失败时抛出 json.JSONDecodeError obj json.load(f) with dst.open(w, encodingutf-8, newline\n) as f: json.dump(obj, f, ensure_asciiFalse, indent2) f.write(\n) if __name__ __main__: format_json_file(config.json)这个脚本里的几个细节值得说encodingutf-8保证中文文件正常读写newline\n防止在 Windows 下写出\r\n换行符保证跨平台 diff 一致最后手动补一个换行符合 Linux 下文本文件以换行结尾的惯例。这个脚本我已经往工程里复制了无数次属于那种“抄作业直接能跑”的工具。3.2 JavaScript 与 Node.js 的格式化方式前端和 Node.js 环境处理 Json 就更直接了JSON.stringify天生支持格式化const obj { name: api-gateway, replicas: 3, endpoints: [/v1/order, /v1/user], deploy: { region: cn-east, canary: true } }; // 第二个参数是替换函数一般传 null第三个参数是缩进空格数 console.log(JSON.stringify(obj, null, 2));输出效果和 Python 类似缩进空格数可以自由控制。JSON.stringify(obj, null, \t)还能用 Tab 缩进但不推荐混用 Tab 和空格格式规范要统一。Node.js 里读取并格式化一个 Json 文件最朴素的写法是const fs require(fs); const raw fs.readFileSync(input.json, utf-8); const obj JSON.parse(raw); // 解析失败直接抛异常 const formatted JSON.stringify(obj, null, 2); fs.writeFileSync(output.json, formatted \n, utf-8);对于很大的 Json 文件JSON.parse在同一线程里会阻塞事件循环导致 Node 服务出现卡顿。如果你要处理几十 MB 甚至上百 MB 的 Json建议拆成流式解析或者干脆丢给 Python/命令行工具处理。大多数场景用不上这个复杂度但心里有这个概念能帮你避开一个隐藏的大坑。3.3 Java 与 C# 的格式化输出Java 世界里最常用的 Json 库无非 Jackson 和 Gson。用 Jackson 格式化输出如下import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; ObjectMapper mapper new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); Object data mapper.readValue(jsonStr, Object.class); String prettyJson mapper.writerWithDefaultPrettyPrinter() .writeValueAsString(data);如果是从零构造对象再输出直接用writerWithDefaultPrettyPrinter()就能在序列化时自动加上缩进。Gson 也有对应的简洁写法import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParser; String prettyJson new GsonBuilder().setPrettyPrinting() .create() .toJson(JsonParser.parseString(jsonStr));这两种写法看起来简单但它们在底层做的事情完全一致先把 Json 字符串解析成内存中的数据结构再按 pretty 模式重新序列化。所以我前面说“格式化自带校验”在 Java 这里体现得很明显——如果原始字符串语法有问题JsonParser.parseString()或者其他 readValue 都会直接抛异常。C#/.NET 里格式化 Json 同样常见。升级到 .NET Core 3.0 之后官方推荐用System.Text.Jsonusing System.Text.Json; var obj JsonSerializer.DeserializeJsonElement(jsonStr); var formatted JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented true });老项目还在用 Newtonsoft.Json 的话就用JTokenusing Newtonsoft.Json; using Newtonsoft.Json.Linq; var json JToken.Parse(jsonStr); string formatted json.ToString(Formatting.Indented);这里插一句热词里有个“C# timespan格式化”很多人搜索时可能把这俩混在一起了。TimeSpan格式化是把时间间隔转成hh:mm:ss这样的字符串它跟 Json 完全是两个话题只是恰好都叫“格式化”而已。如果在 Json 配置里需要表示时长我一般推荐直接用整数秒或者 ISO 8601 字符串不要自己搞自定义格式不然序列化反序列化时容易对不上。3.4 C/Qt 环境处理 Json嵌入式或客户端开发里Qt 的QJsonDocument是高频工具。从 Json 字符串格式化输出的方式是#include QJsonDocument #include QJsonObject #include QFile #include QTextStream QByteArray readFile(const QString path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) return {}; return file.readAll(); } void writePrettyJson(const QString inputPath, const QString outputPath) { QJsonParseError err; auto doc QJsonDocument::fromJson(readFile(inputPath), err); if (err.error ! QJsonParseError::NoError) { qWarning() parse error: err.errorString(); return; } // Indented 模式输出带缩进的 JsonCompact 模式输出压缩形态 QFile out(outputPath); if (out.open(QIODevice::WriteOnly)) { out.write(doc.toJson(QJsonDocument::Indented)); } }QJsonDocument::fromJson()的第二个参数QJsonParseError很重要它能告诉你解析失败的具体位置和原因。我写 Qt 程序时凡是读取配置文件的地方都会把这个错误信息打进日志这样用户反馈配置有问题时我能直接从日志里看到是哪一行哪一列出了问题。热词里提到“qt json struct”和“qt读写json”基本就是这样的操作读入QJsonDocument按字段取出值赋给结构体写回时再序列化。要注意 Qt 自带的toJson(Indented)默认使用 2 个空格缩进跟 Python、JS 的默认行为一致跨语言对齐时基本不用额外处理。Go 语言里则是json.MarshalIndent(data, , )两个空格是社区主流习惯。核心思路都一样先 parse再 serialize中间多做一次语法校验。各种语言的格式化方式可以汇总成下面这张表方便你快速对照查语言/环境核心方法或命令缩进参数备注Pythonjson.dumps(obj, ensure_asciiFalse, indent2)indentjson.tool可直接命令行格式化JavaScript/NodeJSON.stringify(obj, null, 2)第三参数为空格数大文件避免同步 parseJava (Jackson)writerWithDefaultPrettyPrinter().writeValueAsString(obj)默认2空格解析失败会抛异常Java (Gson)new GsonBuilder().setPrettyPrinting().create()默认2空格可链式调用C# (System.Text.Json)JsonSerializer.Serialize(obj, options)WriteIndented trueC# (Newtonsoft)JToken.Parse(str).ToString(Formatting.Indented)默认缩进C/Qtdoc.toJson(QJsonDocument::Indented)默认2空格解析时注意QJsonParseErrorGojson.MarshalIndent(v, , )prefix/indent命令行python -m json.tool in.json/jq . in.json可脚本化建议内网环境使用4. 格式化之后的检查校验、Schema 与前后端联调4.1 格式化成功不等于数据合法很多人格式化完就觉得万事大吉这是个非常危险的误解。格式化只保证语法层面没问题也就是说括号配对、引号闭合、字段名有双引号这些基础语法是对的但格式化不保证业务层面正确比如该有的字段有吗、类型对吗、枚举值合法吗这些格式化工具完全看不出来。举个例子一段请求体{user_id: 123, amount: abc}格式化非常顺利大概率也没有任何一个 JSON 解析器会报错因为字符串abc本身是合法的 Json 值。但如果你把这段发给后端接口后端期望amount是数字类型就会在反序列化阶段报错。这种错误用格式化工具是定位不了的需要靠类型校验和字段校验来做。所以在实际工程里我给自己定的原则是格式化只是第一步校验才是目的。手动审查辅以 Schema 定义才是可靠的思路。4.2 字段缺失类报错的定位思路热词里出现频率很高的一个报错是failed to deserialize the json body into the target type: input: missing fie这句话虽然看着吓人但拆开后就很容易理解服务端要把 Json 反序列化成预期的数据结构结果没找到某个字段于是反序列化失败。注意它说的是missing fie很可能是错误消息被截断了真实内容大概率是missing field xxx。这种问题怎么定位步骤可以固定下来拿到接口请求的原始 Json 文本先格式化确认能正常解析。打开后端服务定义的 DTO/实体类逐个字段和 Json 字段对照找出哪些字段在请求体里不存在。确认字段名是否一致。前端常用 camelCaseuserId后端如果定义成 snake_caseuser_id字段名就对不上。确认字段类型是否匹配。Json 里传了123字符串而后端期望int同样会导致反序列化失败。我在一次真实的联调里遇到过类似情况前端把is_active: 1传了过来后端定义的是bool is_active结果 Json 解析器直接把 1 解析成布尔时失败。这类问题格式化工具看不出任何异常因为语法完全合法。后来我们在团队里强制约定接口文档必须标注每个字段的类型和可空性前后端各拿一份联调之前先对一遍字段清单。4.3 大模型 Json 输出与 Schema 校验的实战最近的热词里涉及到“大模型json schema报错”和“model query failed: 400: failed to deserialize the json body into the target”这些看起来很新的问题本质上还是 Json 格式和 Schema 校验的老问题只是换了个场景。大模型在生成结构化输出时经常因为格式不稳定导致下游解析失败。比如你要模型返回一个对象数组结果它给你补了一句“好的以下是结果”再输出 Json这就在 Json 前面多了非 Json 内容直接解析失败。又或者模型输出过程中字符串被截断出现unterminated stringJSON 解析器同样报错。解决思路有这么几个调接口时要求返回严格 Json设置response_format之类的参数为json_object让服务端尽量约束模型输出。拿到响应后先做一次预处理把代码块标签比如json 和去掉再尝试格式化解析。配合 Json Schema 做字段级校验确认模型返回的内容结构符合预期。热词里面提到的“gpt跳验证码加json解决”本质上也是用 Json 格式约束请求和响应的可预期性规避解析和校验的不确定性。这类问题的排查手段和传统接口联调没有区别先把原始文本保存下来格式化看结构再对照预期的 Schema 找差异。不格式化直接盯着几千个字符找问题是毫无效率可言的。4.4 中文、转义、浮点精度的几个细节处理 Json 格式化时有三个细节最容易出问题这里单独拎出来说。中文显示问题。Python 默认会把中文转成\uXXXXJava 的某些库也有类似行为JavaScript 的JSON.stringify不会转中文原样输出。如果你在 Python 环境里看到一坨\u开头的东西一定记得加ensure_asciiFalse。配置文件、日志、接口文档里中文以可读形式存在永远比转义形式友好。转义问题。Json 字符串内部的引号和反斜杠必须转义比如内容里有双引号时序列化后变成\有换行符时序列化后变成\n。如果你手动拼 Json 而不是用序列化器很容易写出不合法的转义序列。所有正规语言库都会自动处理转义所以我的建议始终是不要手拼 Json 字符串永远先构造对象再序列化。浮点精度问题。Json 传输浮点数时某些高精度场景下会丢失精度。比如你在 Python 里有一个Decimal(0.1)直接序列化成 Json 可能变成 0.1看起来没问题但某些情况下数字会被转换成科学计数法或者丢失尾数。用 Json Schema 校验数字范围时也会遇到边界值问题。这类问题没有统一解法只能在业务层约定好精度、用字符串承载大数或者用allow_nanFalse一类参数把非法浮点值拦截在序列化阶段。5. 高频报错与排查思路速查5.1 常见 Json 报错速查表我把实际工作中遇到的高频 Json 报错整理成了一张表遇到问题可以先查表再决定下一步怎么处理。报错信息常见原因排查思路Expecting property name enclosed in double quotes字段名没加双引号或使用了单引号格式化原始文本找到出错位置给字段名补双引号Expecting : delimiter键值之间缺冒号或前一个值后面多逗号查看分隔符是否正确尤其是多行手写配置时Unterminated string in json at position 8192字符串被截断或引号没有闭合看是不是日志/网关对单条内容有长度限制检查原始数据完整性Unexpected end of JSON inputJson 内容不完整缺括号或数组缺元素确认内容是否被截断补全括号Failed to deserialize the json body into the target type: missing field请求体字段缺失或字段名不匹配对照后端 DTO 字段清单逐字段核对400: model query failed大模型接口请求体结构错误或超限检查是否有多余字段、类型是否正确、内容长度是否超限JSON 解析成功但业务校验失败字段存在但值类型不对或枚举值不合法用 Schema 或文档校验字段类型和取值范围这张表里最容易被忽视的是Unterminated string因为它经常不是 Json 本身写错了而是数据在传输、存储过程中被截断了。位置 8192 这个数字很有代表性很多系统单次消息体超过 8KB 就会被截断所以看到 position 比较大的报错时第一反应应该是“内容完整吗”而不是“语法错在哪”。5.2 从一次真实线上问题看定位流程去年我处理过一次典型的 Json 格式化排查当时服务 A 调用服务 B 的接口返回 500。查 B 服务日志发现只有一行反序列化错误input: missing field items。这行错误出来之后我们并不知道请求体里到底缺了什么于是先让 A 服务把实际发出的请求体打到日志文件里。拿到原始请求体之后第一件事就是格式化。格式化顺利完成说明语法没问题但读起来发现items这个字段确实不在请求体里而且页面展示的productList倒是有一个。最后定位到根因是 A 服务代码里对象名和数据字段对不上的映射错误改一行配置就好了。整个过程耗时大概半小时其中至少有二十分钟是花在“等 A 服务重新打日志”和“格式化原始文本”上。如果 A 服务从一开始就在日志里把 Json 格式化输出我们定位的时间至少能压缩一半。所以我后来特别强调服务里所有打印请求体、响应体的地方统一用缩进格式输出并且控制单条日志大小避免被日志框架截断。5.3 避免踩坑的三条经验第一生产环境打印 Json 日志要克制。格式化输出虽然可读但会显著增加日志体积。你不可能让线上每个接口打印完整请求体否则日志量和存储成本都扛不住。我的做法是调试级别打印完整格式化 Json生产级别只打印业务关键字段和错误信息。第二团队统一格式配置要落地到仓库。.editorconfig和.vscode/settings.json不只是给编辑器用的它代表团队的代码规范。如果不统一你格式化一次同事再格式化一次每次提交都在制造噪音 diff时间长了大家都不敢碰配置文件了。第三写文件时始终保留一个换行符在末尾。很多命令行工具和编辑器对“文件最后是否有换行”非常敏感Python 的json.dump默认不写末尾换行所以我习惯在写完后再补一个\n。这个细节看起来不起眼但对 CI 检查和 Git diff 的整洁度影响很大。6. 一个顺手就能用的小脚本批量格式化目录下的 Json 文件如果手头有一堆 Json 文件需要处理比如一个项目里有几十个配置文件或者从某处下载了一批书源 Json、区域数据 Json手动一个个格式化显然不现实。这里分享一个我常用的 Python 批处理脚本它做的事很简单递归遍历目录、备份原文件、格式化 Json、识别解析失败的文件最后输出统计结果。import json from pathlib import Path def format_dir(root: str, suffix: str .json, backup: bool True): base Path(root) ok_count 0 fail_list [] for src in base.rglob(f*{suffix}): try: with src.open(r, encodingutf-8) as f: obj json.load(f) if backup: bak src.with_suffix(src.suffix .bak) bak.write_text(src.read_text(encodingutf-8), encodingutf-8) with src.open(w, encodingutf-8, newline\n) as f: json.dump(obj, f, ensure_asciiFalse, indent2) f.write(\n) ok_count 1 print(f[OK] {src}) except Exception as e: fail_list.append((str(src), str(e))) print(f[FAIL] {src}: {e}) print(f\n格式化完成成功 {ok_count} 个失败 {len(fail_list)} 个) for path, err in fail_list: print(f - {path}: {err}) if __name__ __main__: format_dir(./configs)脚本里加了backup参数默认在格式化前生成.json.bak备份文件。这是我在处理大批量配置文件时养成的习惯格式化是不可逆的至少对缩进风格来说万一某个文件解析失败或者格式化后业务读取异常还能从备份里快速还原不用去翻版本控制历史。用这个脚本处理过几十个省市区三级联动数据之后我最大的感触是格式化工具本身不复杂但可靠的备份机制和清晰的错误输出才是真正节省时间的地方。脚本的逻辑很简单按需调整就行。7. 再聊几句实在的做 Json 格式化这件事真正考验人的往往不是某个工具不会用而是遇到报错时有没有一套稳定的排查思路。先把原始内容保存下来再格式化再对照 Schema 或字段清单找差异这套流程我用了很多年几乎能覆盖所有 Json 相关的问题。格式化不是目的能通过格式化确认数据结构和预期一致才是背后真正的价值。希望这篇文章里的方法、脚本和踩坑记录能帮你少走一些弯路。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →