OpenCode 实践课:4 分钟用 Go 写出一个命令行 JSON/YAML 格式互转工具

项目介绍

在日常开发中,经常需要在 JSON 和 YAML 两种格式之间切来切去——配置文件是 YAML,API 返回的是 JSON。今天我们用 OpenCode 写一个命令行工具 jsonyaml,能智能检测输入格式并自动转换。

  • 语言:Go
  • 代码量:约 90 行
  • 功能:JSON ↔ YAML 互转,支持文件输入和管道输入,可指定目标格式

全程不用手敲代码,只需要用自然语言指挥 OpenCode 干活。

准备工作

  • 安装 OpenCode(需 Node.js ≥ 18)
  • 安装 Go 1.21+
  • 初始化项目目录

先让 OpenCode 帮我们搭好脚手架:

我对 OpenCode 说
> 帮我创建一个 Go 项目目录 jsonyaml,初始化 go mod,module 名称为 jsonyaml

OpenCode 一键完成:

mkdir jsonyaml && cd jsonyaml && go mod init jsonyaml

终端输出 go: creating new go.mod: module jsonyaml,脚手架就位。

实践过程

第一步:写核心转换逻辑

我对 OpenCode 说
> 帮我写一个 main.go,实现 JSON 和 YAML 之间的互相转换。读取命令行参数作为文件名,根据文件扩展名判断转换方向:.json 文件转成 YAML 输出,.yaml/.yml 文件转成 JSON 输出。使用 gopkg.in/yaml.v3 库。

OpenCode 生成代码后,自动执行 go mod tidy 拉取依赖。它理解了我的意图——通过文件扩展名自动判断转换方向,不需要手动指定。

关键要点:

  • OpenCode 自动处理了 go mod tidy,无需我们手动下载依赖
  • 错误处理写得规范,解析失败时会给出明确提示

第二步:加入管道输入

这个工具只能读文件不够灵活,让我加上管道输入支持。

我对 OpenCode 说
> 当没有提供命令行参数时,从标准输入读取内容,这样就能用管道 cat a.json | jsonyaml 的方式使用。同时添加 -h 帮助选项和 -t 选项强制指定目标格式。

OpenCode 引入了 flag 包,写了 io.ReadAll(os.Stdin) 处理管道,还加了一个 detectTarget 函数:当未指定 -t 时,先用 json.Unmarshal 试探——成功说明是 JSON,就转 YAML;失败就当 YAML 处理转 JSON。

关键要点:

  • 自动格式检测的思路很巧妙:不是看扩展名,而是直接尝试解析
  • detectTarget 函数体现了"先试 JSON,不行再试 YAML"的容错策略

第三步:加个 JSON 缩进控制

我对 OpenCode 说
> JSON 输出默认是一行,不太好看。加一个 -indent 参数控制缩进,默认用两个空格。同时重构一下,把转换逻辑抽成 toJSON 和 toYAML 两个独立函数。

OpenCode 用 json.MarshalIndent 替代了 json.Marshal,默认缩进两个空格。同时把转换逻辑拆成了 toJSONtoYAML,代码结构更清晰。

关键要点:

  • 函数拆分后,每个函数只做一件事,符合单一职责
  • 如果你觉得缩进太大,-indent " " 就能改成 4 空格

第四步:测试运行

我对 OpenCode 说
> 帮我创建测试数据,先用 jsonyaml 把 JSON 转成 YAML,再转回来验证一致。

OpenCode 创建了 test.json,然后运行转换:

go run main.go test.json
# 输出 YAML 格式的结果

go run main.go test.json | go run main.go -t json
# 把 YAML 结果管道传回去转成 JSON,验证数据一致

输出完全一致,转换无损。

第五步(彩蛋):一键编译

我对 OpenCode 说
> 帮我编译成可执行文件,输出到当前目录

go build -o jsonyaml.exe .

一个 3MB 左右的独立可执行文件就生成了,无需 Go 环境也能跑。

完整代码

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"os"
	"strings"

	"gopkg.in/yaml.v3"
)

var (
	toFormat = flag.String("t", "", "目标格式: json 或 yaml")
	indent   = flag.String("indent", "  ", "JSON 缩进")
	help     = flag.Bool("h", false, "显示帮助")
)

func main() {
	flag.Parse()

	if *help {
		printHelp()
		return
	}

	var input []byte
	var err error

	if flag.NArg() > 0 {
		input, err = os.ReadFile(flag.Arg(0))
	} else {
		input, err = io.ReadAll(os.Stdin)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "读取输入失败: %v\n", err)
		os.Exit(1)
	}

	target := strings.ToLower(*toFormat)
	if target == "" {
		target = detectTarget(input)
	}

	var result []byte
	switch target {
	case "json":
		result, err = toJSON(input, *indent)
	case "yaml", "yml":
		result, err = toYAML(input)
	default:
		fmt.Fprintf(os.Stderr, "未知目标格式: %s (支持 json / yaml)\n", target)
		os.Exit(1)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "转换失败: %v\n", err)
		os.Exit(1)
	}

	fmt.Print(string(result))
}

func detectTarget(data []byte) string {
	var obj interface{}
	if json.Unmarshal(data, &obj) == nil {
		return "yaml"
	}
	return "json"
}

func toJSON(data []byte, indent string) ([]byte, error) {
	var obj interface{}
	if err := yaml.Unmarshal(data, &obj); err != nil {
		return nil, fmt.Errorf("YAML 解析失败: %w", err)
	}
	return json.MarshalIndent(obj, "", indent)
}

func toYAML(data []byte) ([]byte, error) {
	var obj interface{}
	if err := json.Unmarshal(data, &obj); err != nil {
		return nil, fmt.Errorf("JSON 解析失败: %w", err)
	}
	return yaml.Marshal(obj)
}

func printHelp() {
	fmt.Println(`jsonyaml - JSON 与 YAML 格式互转工具

用法:
  jsonyaml [选项] [文件]

选项:
  -t       指定目标格式 (json 或 yaml),不指定则自动检测
  -indent  JSON 缩进字符串 (默认: "  ")
  -h       显示此帮助信息

示例:
  jsonyaml data.json           # JSON → YAML
  jsonyaml config.yaml         # YAML → JSON
  cat data.json | jsonyaml     # 管道输入
  jsonyaml -t json config.yml  # 强制转换为 JSON`)
}

小结

这个小工具从头到尾没写一行代码,全程用自然语言和 OpenCode 对话完成:

| 过程 | 传统开发 | 用 OpenCode |
|------|---------|------------|
| 初始化项目 | 手动敲命令 | 一句话 |
| 核心逻辑 | 查文档、写代码、调 Bug | 描述需求 |
| 添加功能 | 改多处、担心遗漏 | 追加描述 |
| 测试验证 | 手动构造数据 | 让它帮你做 |
| 编译部署 | 记命令参数 | 一句话 |

4 分钟,90 行 Go,一个立刻能用的 JSON/YAML 互转工具。用 OpenCode 开发就像有个高级工程师坐旁边——你只需要把脑子里的需求说清楚,代码、测试、编译它全包了。