在日常开发中,经常需要在 YAML 和 JSON 两种格式之间来回切换——配置文件是 YAML,API 接口返回的是 JSON。今天我们用 OpenCode 花 4 分钟写一个命令行工具,一行命令就能把 YAML 转成 JSON。
技术栈:Go + gopkg.in/yaml.v3,最终代码约 90 行,支持紧凑输出和自定义输出文件。
需要本地安装 Go(1.18+),并初始化项目:
mkdir y2j && cd y2j go mod init y2j
OpenCode 安装配置请参考 opencode.ai,安装完成后在终端运行 opencode 即可进入交互模式。
打开 OpenCode,输入第一条指令:
> 我对 OpenCode 说:
> "帮我用 Go 写一个 YAML 转 JSON 的命令行工具,读取指定的 YAML 文件,输出格式化的 JSON。使用 gopkg.in/yaml.v3 库。"
OpenCode 先执行 go get gopkg.in/yaml.v3 安装依赖,然后生成 main.go,核心逻辑是 yaml.Unmarshal 解析 YAML,json.MarshalIndent 输出 JSON。代码生成后 OpenCode 自动运行 go build 验证编译。
关键要点:用 OpenCode 开发第三方库项目时,它自动帮你处理依赖安装,不需要手动 go get。
基础功能跑通后,继续发号施令:
> 我对 OpenCode 说:
> "加上两个参数:-c 输出紧凑 JSON(不换行不缩进),-o 指定输出文件路径,默认输出到终端。"
OpenCode 引入 flag 标准库,添加了 -c 和 -o 两个 flag,并根据 -output 参数判断是写入文件还是打印到 stdout。修改完成后自动重新编译验证。
关键要点:OpenCode 理解"紧凑 JSON"指的就是 json.Marshal(不带缩进),无需你解释技术细节。
测试时发现了一个坑——用 any(interface{})接收解析结果时,YAML 的 map key 会被解析为 map[any]any 而非 map[string]any,导致 JSON 序列化失败。
> 我对 OpenCode 说:
> "运行报错了,json: unsupported type: map[interface {}]interface {},帮我修复这个类型转换问题。"
OpenCode 添加了 convertKeys 递归函数,遍历解析结果并将 map[any]any 转换为 map[string]any,问题解决。
关键要点:把报错信息直接贴给 OpenCode,它能精准定位问题并修复——就像 pair programming 一样自然。
最后做个收尾:
> 我对 OpenCode 说:
> "帮我把帮助信息写完整,include 使用示例;另外输出文件末尾要加换行。"
OpenCode 重写了 flag.Usage,添加了清晰的用法说明和示例,并在输出末尾处理了换行符。
至此,一个实用的 YAML 转 JSON 命令行工具就完成了。整个过程没有手写一行代码——全部由 OpenCode 生成。
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
var (
compact = flag.Bool("c", false, "compact output (no indentation)")
output = flag.String("o", "", "write to file instead of stdout")
)
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "y2j - Convert YAML to JSON\n\n")
fmt.Fprintf(os.Stderr, "Usage:\n y2j [options] <input.yaml>\n\n")
fmt.Fprintf(os.Stderr, "Examples:\n y2j config.yaml\n")
fmt.Fprintf(os.Stderr, " y2j -c config.yaml\n")
fmt.Fprintf(os.Stderr, " y2j -o output.json config.yaml\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() < 1 {
flag.Usage()
os.Exit(1)
}
data, err := os.ReadFile(flag.Arg(0))
if err != nil {
fmt.Fprintf(os.Stderr, "read error: %v\n", err)
os.Exit(1)
}
var doc any
if err := yaml.Unmarshal(data, &doc); err != nil {
fmt.Fprintf(os.Stderr, "yaml parse error: %v\n", err)
os.Exit(1)
}
doc = convertKeys(doc)
var jsonBytes []byte
if *compact {
jsonBytes, err = json.Marshal(doc)
} else {
jsonBytes, err = json.MarshalIndent(doc, "", " ")
}
if err != nil {
fmt.Fprintf(os.Stderr, "json encode error: %v\n", err)
os.Exit(1)
}
out := os.Stdout
if *output != "" {
f, err := os.Create(*output)
if err != nil {
fmt.Fprintf(os.Stderr, "write error: %v\n", err)
os.Exit(1)
}
defer f.Close()
out = f
}
result := string(jsonBytes)
if !strings.HasSuffix(result, "\n") {
result += "\n"
}
fmt.Fprint(out, result)
}
func convertKeys(val any) any {
switch v := val.(type) {
case map[string]any:
m := make(map[string]any, len(v))
for k, val := range v {
m[k] = convertKeys(val)
}
return m
case map[any]any:
m := make(map[string]any, len(v))
for k, val := range v {
m[fmt.Sprintf("%v", k)] = convertKeys(val)
}
return m
case []any:
slice := make([]any, len(v))
for i, item := range v {
slice[i] = convertKeys(item)
}
return slice
}
return val
}
这个小工具从零到可用,全程只用了 4 分钟和 4 条自然语言指令。体会最深的有三点:
依赖管理自动化:OpenCode 识别到第三方库后自动 go get,无需手动操作
报错即修复:遇到运行时错误,把错误信息丢给 OpenCode 就能得到针对性修复
迭代式开发:从基础功能到参数控制到类型处理到体验完善,像和人对话一样逐步打磨
相比传统开发模式——查文档、写代码、调试、改 bug、再调试——用 OpenCode 的开发节奏更像"口述需求 + 验收结果",把精力集中在定义问题和验证输出,而非实现细节。
下次需要快速造个实用轮子,不妨试试用 OpenCode 把想法直接说出来。