news 2026/9/20 2:14:33

chezmoi 模板函数 fromJsonc 详解:在 dotfiles 模板中解析带注释的 JSONC 数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
chezmoi 模板函数 fromJsonc 详解:在 dotfiles 模板中解析带注释的 JSONC 数据

chezmoi 模板函数 fromJsonc 详解:在 dotfiles 模板中解析带注释的 JSONC 数据

【免费下载链接】chezmoiManage your dotfiles across multiple diverse machines, securely.项目地址: https://gitcode.com/gh_mirrors/ch/chezmoi

fromJsonc是 chezmoi 模板引擎中用于将 JSONC(Human JSON,即带注释与尾随逗号的 JSON 超集)文本解析为结构化数据的模板函数。它让模板作者可以直接读取带有注释、尾随逗号等人类友好写法的配置文件与数据源,并像使用普通 map/slice 一样访问其中的字段。读完本文,你将掌握fromJsonc的完整签名、底层解析原理、数字类型转换规则、实际模板用法及其在 chezmoi 代码库内部的真实应用场景。

函数签名与核心语义

根据官方函数参考文档 fromJsonc.md,fromJsonc的签名定义如下:

fromJsonc jsonctext
  • 参数jsonctext,一个字符串,内容是待解析的 JSONC 文本。
  • 返回值:解析得到的值(对象、数组、字符串、数字或布尔值)。
  • 解析引擎:底层使用github.com/tailscale/hujson库完成 JSONC 解析,该依赖在 go.mod 中声明。

简单来说,fromJsonc与模板函数fromJson行为一致,但额外兼容 JSONC 语法扩展:允许//行注释、/* ... */块注释以及对象与数组末尾的尾随逗号(trailing comma),这正是诸如 VS Code、微软 WinGet 等工具配置文件所采用的"Human JSON"风格。

JSONC 与标准 JSON 的区别

标准 JSON(RFC 7159)是严格的纯数据格式,不允许任何注释和尾随逗号;而 JSONC(JSON with Comments)是它的超集,允许:

  1. //单行注释;
  2. /* ... */多行块注释;
  3. 数组和对象最后一个元素之后的逗号(尾随逗号)。

例如下面这份文本是合法的 JSONC,但无法被标准 JSON 解析器接受:

{ "key": 1, // Comment }

该示例正是 chezmoi 测试套件中用于验证fromJsonc的输入数据,见 templatefuncs.txtar。

底层实现原理

模板函数入口

fromJsonc的模板函数实现位于 templatefuncs.go:

// fromJsoncTemplateFunc parses s as JSONC and returns the result. In contrast // to encoding/json, numbers are represented as int64s or float64s if possible. func (c *Config) fromJsoncTemplateFunc(s string) any { var value any must(chezmoi.FormatJSONC.Unmarshal([]byte(s), &value)) return value }

实现非常简洁:将输入字符串转为字节切片,交给 chezmoi 内部定义的FormatJSONC反序列化器,结果存入any类型变量返回。解析失败时通过must抛出 panic,与 chezmoi 其他模板函数(如fromJsonfromYaml)的错误处理风格保持一致。

函数注册

fromJsonc在模板函数注册表中与众多内建函数一起注册,见 config.go:

"fromJsonc": c.fromJsoncTemplateFunc,

因此它可以在任何 chezmoi 模板上下文中直接使用,包括chezmoi execute-template、源文件模板、chezmoi data输出等。文档站点配置 mkdocs.yml 也将其注册为独立的参考页面。

JSONC 反序列化的两步流水线

FormatJSONC由 format.go 中的formatJSONC类型实现,其Unmarshal采用"先标准化、再按 JSON 解析"的两步策略:

// Unmarshal implements Format.Unmarshal. func (formatJSONC) Unmarshal(data []byte, value any) error { data, err := hujson.Standardize(data) if err != nil { return err } return FormatJSON.Unmarshal(data, value) }
  1. 标准化(Standardize)hujson.Standardize将 JSONC 文本中的注释剥离、移除尾随逗号,并规范空白,产出一份等价的标准 JSON;
  2. 标准解析:标准化结果交给FormatJSON.Unmarshal(即 format.go 中的严格 JSON 解码器)完成真正的解析。

formatJSONC同时实现了Marshal(见 format.go):先用 Go 标准库json.Encoder编码(关闭 HTML 转义),再用hujson.Format输出人类友好的 JSONC 排版。这意味着FormatJSONC在 chezmoi 内部被当作一种一等序列化格式对待,与jsontomlyaml并列,注册于FormatsByNameFormatsByExtension映射中(见 format.go)。

严格的 JSON 解码细节

FormatJSON.Unmarshal使用了json.DecoderDisallowUnknownFields(),并在解码到通用类型时启用UseNumber()以保留数字精度,随后做一次"只允许单一顶层值"的 EOF 校验(见 format.go)。这些约束同样作用于fromJsonc的解析结果。

数字类型转换规则

fromJsoncfromJson一样,在数字处理上比 Go 标准库encoding/json更智能。根据 format.go 的replaceJSONNumbersWithNumericValues逻辑,解析出的 JSON 数字按如下优先级转换:

  1. 能被精确表示为 64 位有符号整数(int64)的数字,返回int64
  2. 否则,若在 64 位 IEEE 浮点数范围内,返回float64
  3. 否则(超出两者表示范围),以字符串形式返回,以保留原始数值,如 format.go 注释所述(这类值合法但实际罕见,参见 RFC 7159 Section 6)。

这一规则与 fromJson.md 中fromJson的行为完全一致,保证模板中做数值运算(如addmul)时能拿到真正的数字而非字符串。

实战用法

在 execute-template 中解析标准输入

最直接的用法是借助--with-stdin将文件内容注入模板上下文.chezmoi.stdin,再用fromJsonc解析。chezmoi 的集成测试 templatefuncs.txtar 给出了完整可复现的示例:

// 输入文件 example.jsonc { "key": 1, // Comment }
chezmoi execute-template --with-stdin '{{ fromJsonc .chezmoi.stdin | toJson }}' # 输出 {"key":1}

可以看到,输入中的// Comment注释被正确剥离,toJson输出为标准 JSON 格式。

在模板文件中读取带注释的配置

fromJsonc也常用于源文件模板内部,读取机器上已有的 JSONC 风格配置文件作为模板数据。例如:

{{- $config := fromJsonc (include "~/.config/editor/settings.jsonc") -}} {{ $config.theme | default "dark" }}

这里的include负责读取文件内容(支持~展开),fromJsonc将其解析为字典,随后即可通过点路径访问嵌套字段,配合default提供回退值。

结合管道与其他函数使用

由于fromJsonc返回值是any类型,天然适合接入管道,配合toJsontoYamlindexhasKeyget等函数做后续处理。比如将 JSONC 配置整体转成 YAML 输出:

chezmoi execute-template --with-stdin '{{ fromJsonc .chezmoi.stdin | toYaml }}'

错误处理与边界情况

fromJsonc对非法输入直接报错(panic 后转为 chezmoi 错误消息)。format_test.go 用一组表格测试完整覆盖了FormatJSONC的边界行为,可作为排查解析问题的参考:

输入结果
{"key":"value"} // comment解析成功,值为{"key":"value"}
{"key":"value"}解析成功
空输入报错parsing value: unexpected EOF
{"key":"value"}1(顶层多余值)报错invalid character '1' after top-level value
{"unknown":"value"}(未知字段)报错json: unknown field "unknown"
{(意外 EOF)报错parsing value: unexpected EOF
"\n"(仅空白)报错parsing value: unexpected EOF

这些用例揭示了几条实用结论:

  • 顶层必须恰好是一个值:JSONC 内容之后若还有多余 token(如}1)会失败,避免静默丢弃数据;
  • 不允许未知字段:在需要严格校验结构的场景中(结合具名结构体解析),DisallowUnknownFields会拒绝未声明的键;
  • 空白与空输入视为错误:与fromYaml对空输入宽容不同(见 format.go),fromJsonc要求内容非空。

在 chezmoi 代码库中的真实应用

fromJsonc的底层格式FormatJSONC并非仅服务模板函数,它还被 chezmoi 自身用于解析真实世界中的 JSONC 文件。最典型的例子在 upgradecmd_windows.go:chezmoi 在 Windows 上升级时,会读取微软 WinGet 的settings.json(该文件允许注释,属 JSONC 格式)来判断可移植包安装位置:

settingsPaths := []string{ os.ExpandEnv(`${LOCALAPPDATA}\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json`), os.ExpandEnv(`${LOCALAPPDATA}\Microsoft\WinGet\Settings\settings.json`), } for _, settingsPath := range settingsPaths { if _, err := os.Stat(settingsPath); err == nil { winGetSettingsContents, err := os.ReadFile(settingsPath) if err == nil { if err := chezmoi.FormatJSONC.Unmarshal(winGetSettingsContents, &winGetSettings); err != nil { return false, err } } } }

这印证了FormatJSONC(即fromJsonc的底层)对"人类可读配置"的解析能力是经过真实场景验证的——同样是"带注释的 JSON 配置文件",在模板中你可以用fromJsonc,在 Go 代码中可以直接用chezmoi.FormatJSONC.Unmarshal

与其他解析函数的对比与选择

chezmoi 提供了完整的反序列化函数家族,注册表见 config.go:

函数解析格式典型场景
fromJson严格 JSON标准的 JSON API 响应、纯 JSON 配置
fromJsoncJSONC(注释 + 尾随逗号)允许注释的配置文件(如 WinGet、VS Code 风格)
fromTomlTOMLRust/Cargo 风格的配置文件
fromYamlYAMLKubernetes、Ansible 等 YAML 生态

选型建议:当数据来源可能带注释、或来源是人工维护的 JSONC 配置时用fromJsonc;当数据来自程序生成的严格 JSON 时用fromJson即可。若数据可能包含注释但不确定,直接选用fromJsonc是更稳妥的选择,因为它兼容标准 JSON 的全部语法,仅是放宽了注释与尾随逗号的限制。

小结

fromJsonc以极小的 API 面(一个字符串参数、一个任意类型返回值)提供了 JSONC 解析能力,其背后是"hujson 标准化 + 严格 JSON 解码"的两步流水线,以及 int64/float64/string 三级数字保真策略。无论是通过chezmoi execute-template --with-stdin快速解析带注释的配置,还是在源文件模板中读取机器上的 JSONC 文件,fromJsonc都让"人类友好的 JSON"与"模板可用的结构化数据"无缝衔接,是处理配置类数据源时值得优先考虑的工具。

【免费下载链接】chezmoiManage your dotfiles across multiple diverse machines, securely.项目地址: https://gitcode.com/gh_mirrors/ch/chezmoi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 2:14:23

VSCODE Ctrl+左键跳转失灵?语言服务器与索引配置全解析

1. 问题定位:先搞清楚“跳不了”到底卡在哪一层VSCODE 里Ctrl左键点函数名、类名、变量名,本该直接跳到定义处,结果要么毫无反应,要么底部状态栏弹出一句“正在初始化重新扫描工作区”,要么跳到一个空文件、错误位置&a…

作者头像 李华
网站建设 2026/9/20 2:13:44

npx add-skill 实战:Agent Skill 安装、版本管理与常见报错排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 2:13:36

多端同步的 CodeX,换到 TaoToken 通道行不行?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 2:13:07

双闭环晶闸管直流调速系统:PI整定与Python仿真复现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 2:12:57

AI+无代码5天交付小程序MVP:从需求到上线的完整路径

1. MVP选型第一课:为什么小程序适合用AI无代码快速验证上个月,一个做社区餐饮的老板找到我,说想上一个小程序做点餐,预算不高、时间又急,最好两周内能拿出一个能给别人演示的东西。以前接到这种需求,我的第…

作者头像 李华