Cataclysm-DDA JSON 样式规范与格式化工具实战指南
【免费下载链接】Cataclysm-DDACataclysm - Dark Days Ahead. A turn-based survival game set in a post-apocalyptic world.项目地址: https://gitcode.com/GitHub_Trending/ca/Cataclysm-DDA
Cataclysm-DDA(《大灾变:黑暗之日》)是一款以海量 JSON 数据驱动玩法内容的后末日回合制生存游戏,其data/json与data/mods目录下沉淀了数千个玩法定义文件。本文以仓库中的 doc/JSON/JSON_STYLE.md 为骨架,系统讲解官方 JSON 排版规范、120 字符换行规则的底层实现,以及自研格式化工具json_formatter的构建、命令行使用与 IDE 集成方案。读完本文,你将能够把任何 CDDA 数据文件一键格式化为符合官方 CI 校验的风格,并在 Visual Studio / VS Code 中搭建自动 lint 工作流。
JSON 样式规范:与 C++ 代码风格同源的工程约束
与 doc/c++/CODE_STYLE.md 中的代码风格策略一致,Cataclysm-DDA 的 JSON 样式策略是:新增或编辑 JSON 时立即按规范格式化;对存量文件则分小批量逐步处理,避免对开发造成过大扰动。这一策略保证了 PR 评审时 diff 干净、可读。
由于团队始终没有找到令人满意的现成 JSON 格式化工具,他们干脆自己写了一个。该工具位于 tools/format/format.cpp,并直接复用游戏自身的 JSON 解析与输出实现 src/json.cpp 来完成解析(parse)与重排(emit)——这意味着格式化器的行为与游戏加载数据时的行为天然一致,不会出现"格式化后游戏读不了"的偏差。发布版本中通常会附带已编译好的json_formatter.cgi(或 Windows 下的json_formatter.exe)。
核心样式规则速览
以下六条规则构成了 CDDA JSON 的全部排版约定:
- 缩进为两个空格(对应 src/json.cpp 中
JsonOut::write_indent按缩进层级写入indent_level * 2个空格); - 除逗号(
,)和冒号(:)外的所有 JSON 定界符两侧都要有空白(空格或换行); - 逗号和冒号后面必须跟空白(冒号后固定为单个空格,见 src/json.cpp 的
write_member_separator); - 对象成员之间永远换行分隔;
- 数组元素在"结果数组总长(含缩进)超过 120 字符"时才换行分隔,否则保持单行紧凑排列;
- 换行只能出现在开括号之后、闭括号之后或元素/成员之后。
这些规则全部由格式化工具强制执行,因此手写时不必死记,只需让工具重排即可。
官方示例逐段解读
原文档给出的示例几乎覆盖了全部排版特征(缩进、短数组、短对象、长数组换行、嵌套数组换行):
[ { "type": "foo", "id": "example", "short_array": [ 1, 2, 3, 4, 5 ], "short_object": { "item_a": "a", "item_b": "b" }, "long_array": [ "a really long string to illustrate line wrapping, ", "which occurs if the line is longer than 120 characters" ], "nested_array": [ [ [ "item1", "value1" ], [ "item2", "value2" ], [ "item3", "value3" ], [ "item4", "value4" ], [ "item5", "value5" ], [ "item6", "value6" ] ] ] } ]逐项解读:
- 顶层:整个文件是一个数组,每个元素通常是一个带
type与id字段的对象定义; short_array:[ 1, 2, 3, 4, 5 ]整体不足 120 字符,因此保持单行,且[、]两侧留白、元素间以,分隔;short_object:对象成员强制换行,因此即使是两个短键值对也写成多行——这是规则 4 的直接体现;long_array:如果按单行拼接,整行将超过 120 字符,于是每个元素独立成行;nested_array:多层嵌套数组逐层缩进两格,内层[ "item1", "value1" ]这类短对保持单行。
120 字符换行规则的源码实现
"先尝试紧凑排列,超长再回退为逐行展开"这一行为,在 tools/format/format.cpp 的format_collection中实现得非常巧妙:
if( depth > 1 && !force_wrap ) { // 先保存输入输出流的位置与状态 int in_start_pos = jsin.tell(); bool ate_separator = jsin.get_ate_separator(); int out_start_pos = jsout.tell(); bool need_separator = jsout.get_need_separator(); write_func( jsin, jsout, depth, false ); // 先按紧凑模式序列化 if( jsout.tell() - out_start_pos <= 120 ) { return; // 未超 120 字符,紧凑方案直接保留 } else { // 回退:恢复输入输出流位置,改为强制换行模式重新输出 jsin.seek( in_start_pos ); jsin.set_ate_separator( ate_separator ); jsout.seek( out_start_pos ); if( need_separator ) { jsout.set_need_separator(); } } } write_func( jsin, jsout, depth, true ); // force_wrap = true即:先尝试把数组/对象写在同一行,测量输出流增量长度,超过 120 字符就"撤销"并强制换行重写。这正是文档中"数组元素在结果数组超过 120 字符(含缩进)时换行"的精确语义。
此外,write_object(tools/format/format.cpp)对rows、blueprint、picture三个特殊字段做了内省处理:当这些字段对应的数组多于一个元素时,强制其换行展开。这三个字段正是 CDDA 地图生成(mapgen)JSON 中常见的"逐行地图字符画"结构,强制换行能让这类数据始终可读。
格式化主流程formatter::format(tools/format/format.cpp)按类型分派:数组/对象走上述集合逻辑,字符串、数字、布尔、null分别处理。其中有两个值得注意的细节:
- 字符串:原样保留转义序列,并将不换行空格(U+00A0)替换为
\u00A0转义,避免非 ASCII 空白混入数据造成混淆; - 数字:整数与浮点分开序列化。浮点输出会去掉多余尾零,同时保证"要么带小数点,要么小数点后补一个 0"(如
5.0),从而稳定地区分整数与浮点类型。
格式化工具:获取与构建
获取json_formatter有三种途径:
- 发布包:任何正式发布的构建中都附带已编译好的
json_formatter.cgi(Linux/跨平台)或json_formatter.exe(Windows); - 源码构建:执行
make style-json,Makefile(Makefile)会先编译出tools/format/json_formatter(Windows 下为.exe)再运行校验;CMake 工程同样提供了json_formatter可执行目标,见 tools/format/CMakeLists.txt,它把 tools/format/format.cpp、tools/format/format_main.cpp 与游戏自身的 src/json.cpp 编译在一起; - Web 工具:仓库自带的 tools/format/format.html 是一个"粘贴 JSON → 点击 Lint → 回填格式化结果"的网页工具,前端通过 POST 把
data参数发送给json_formatter.cgi(10 秒超时,分别处理 200/304/400 响应)。
官方建议将工具路径加入PATH环境变量;如果尚未加入,也可以把它放到仓库根目录,方便在任何子目录调用。
命令行使用方式
工具支持单文件格式化、标准输入(stdin)与 CGI 三种运行模式(见 tools/format/format_main.cpp):
# 格式化单个文件 path/to/json_formatter path/to/file/to/format # 不带参数时从标准输入读取,格式化结果写到标准输出 cat data/json/example.json | path/to/json_formatter # 便捷做法:把 json_formatter 放到固定位置并配置 shell 别名/缩写, # 这样在任意目录下都能直接调用Windows 用户还可以把json_formatter.exe拖放到目标文件上,即可一键格式化该文件。
工具的行为逻辑(tools/format/format_main.cpp)值得特别注意:
- 若文件已符合规范:直接以退出码 0结束,不触碰文件;
- 若文件需要重排:就地覆写文件,输出
Has been linted : <文件名>与Please read doc/JSON/JSON_STYLE.md,并以非零退出码结束。
因此它既是格式化器,也是一个可嵌入 CI 的 lint 检查器——非零退出码即表示"此文件未通过样式校验"。
配合 git 与 find 的批量用法(原文档给出的命令,全部保留):
# 用 git 过滤出有未提交改动的 JSON 文件(要求文件/目录名不含空格) git diff --name-only '*.json' | xargs -P 0 -L 1 json_formatter # 用 git 过滤出当前分支相对 master 修改过的 JSON 文件 git diff master --name-only '*.json' | xargs -P 0 -L 1 json_formatter # 按目录批量格式化 find path/to/desired/folder -name "*.json" -print0 | xargs -P 0 -0 -L 1 json_formatterxargs -P 0表示按 CPU 数量并行执行,格式化数千个 JSON 文件也能在很短时间内完成。
此外,Makefile 还提供了两个更省心的目标(Makefile):
# 格式化 data 目录下全部 JSON(串行) make style-all-json # 并行版,按 CPU 核数加速 make style-all-json-parallel而make style-json只校验/格式化"JSON 校验测试覆盖的文件"(即data目录下的全部*.json),并且已被纳入make checks的CHECKS列表(见 Makefile),因此本地跑完整检查时会自动包含 JSON 样式校验。
底层实现:JsonOut 的缩进、分隔与转义
格式化输出的排版细节全部由JsonOut控制,位于 src/json.cpp:
- 缩进:
write_indent每级写入indent_level * 2个空格(src/json.cpp); - 逗号与换行:
write_separator(src/json.cpp)先写逗号,然后依据"顶层(indent_level < 2)或处于强制换行容器内"决定补换行还是补空格——这正是"短数组单行、长数组多行"统一由need_wrap栈驱动的实现; - 冒号:
write_member_separator固定输出": "(src/json.cpp); - 括号:
start_pretty/end_pretty(src/json.cpp)在打开/关闭数组或对象时决定换行与缩进,其中"退出包含对象的数组时强制换行"是特殊分支; - 字符串转义:
JsonOut::write(src/json.cpp)对引号、反斜杠、控制字符等做标准 JSON 转义,小于 0x20 的字符统一输出为\u00xx形式。
正是这套实现保证了格式化输出既是合法 JSON,又严格符合上文六条排版规则。
Visual Studio 集成:构建即校验
原文档针对 Windows 开发者给出了完整的 Visual Studio 配置方案,分为三步:
第 1 步:构建 JsonFormatter 工程。在 VS 解决方案中构建整个解决方案或仅构建JsonFormatter工程,会生成tools/format/json_formatter.exe。
第 2 步:启用自动 lint。定义一个 MSBuild 变量CDDA_POST_BUILD_JSON_LINT(类似于在 VS 中配置 ccache 的做法)。最简单的方式是在仓库根目录创建Directory.Build.props:
<Project> <PropertyGroup> <CDDA_POST_BUILD_JSON_LINT>true</CDDA_POST_BUILD_JSON_LINT> </PropertyGroup> </Project>配置成功后,构建或运行解决方案时会自动触发 linter,Output与Error List窗口会列出不合规的文件。其背后实际执行的是 msvc-full-features/style-json.ps1:该脚本通过git diff --name-only '*.json'及多个上游分支的 merge-base 探测出改动文件,逐个调用json_formatter.exe;从 MSBuild 调用时会把工具输出解析成文件(行,列) : lint warning cddalint01 : 消息的 VS 错误日志格式,并设置了 30 秒超时保护,避免长时间阻塞构建。
第 3 步:添加手动 lint 菜单项。通过Tools>External Tools..>Add添加外部工具:
- Title:
Lint All JSON - Command:
C:\windows\system32\windowspowershell\v1.0\powershell.exe - Arguments:
-file $(SolutionDir)\style-json.ps1 - Initial Directory:
$(SolutionDir) - Use Output window:勾选
之后即可通过Tools>Lint All JSON菜单手动触发;若想绑定快捷键,可到Tools>Options>Environment>Keyboard搜索Tools.ExternalCommand,找到对应位置的命令(例如列表第一项Tools.ExternalCommand1)并分配快捷键。
Visual Studio Code 集成
在 VS Code 中安装项目推荐的扩展(cdda-toys扩展集)后,会获得cdda-toys.cdda-json-formatter,它对 JSON 文件提供"保存即自动格式化"的能力,无需手动配置命令行参数。
小结
CDDA 的 JSON 样式体系可以概括为"一个自研工具 + 六条排版规则 + 多套集成入口":规则简洁(两空格缩进、定界符留白、对象换行、数组按 120 字符智能换行),工具可靠(复用游戏自身的json.cpp解析器,保证格式与加载行为一致),入口丰富(命令行、git/find 批量管道、Makefile 目标、VS 构建钩子、VS Code 扩展、网页工具)。
如需进一步深入,可继续阅读:
- 规范原文:doc/JSON/JSON_STYLE.md
- 格式化器实现:tools/format/format.cpp、tools/format/format_main.cpp
- 输出排版底层:src/json.cpp
- Makefile 相关目标:Makefile
- Windows lint 脚本:msvc-full-features/style-json.ps1
- C++ 侧代码风格约定:doc/c++/CODE_STYLE.md
【免费下载链接】Cataclysm-DDACataclysm - Dark Days Ahead. A turn-based survival game set in a post-apocalyptic world.项目地址: https://gitcode.com/GitHub_Trending/ca/Cataclysm-DDA
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考