
本文介绍如何在 Go 中将任意结构的 YAML 字符串安全、递归地转换为标准 JSON 字符串,适用于配置动态、无法提前定义 struct 的场景,核心是解决 map[interface{}]interface{} 导致的 json.Marshal 不支持问题。
本文介绍如何在 go 中将任意结构的 yaml 字符串安全、递归地转换为标准 json 字符串,适用于配置动态、无法提前定义 struct 的场景,核心是解决 `map[interface{}]interface{}` 导致的 `json.marshal` 不支持问题。
在 Go 中使用 gopkg.in/yaml.v3(或 github.com/go-yaml/yaml)解析未知结构的 YAML 时,最自然的方式是反序列化到 interface{} 类型:
var body interface{}
err := yaml.Unmarshal([]byte(yamlStr), &body)但此时 body 内部嵌套的映射默认为 map[interface{}]interface{} —— 而 Go 标准库的 json.Marshal 明确不支持该类型,会 panic 报错:json: unsupported type: map[interface {}]interface {}。
根本原因在于:YAML 解析器为保持通用性,对所有键值对统一使用 interface{} 作为 map 的 key 类型;而 JSON 规范要求对象键必须为字符串,encoding/json 拒绝处理非字符串键的 map。
✅ 正确解法是:递归遍历 interface{} 值,将所有 map[interface{}]interface{} 转换为 map[string]interface{},同时递归处理 slice 和嵌套结构。
以下是一个健壮、简洁的转换函数:
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := make(map[string]interface{})
for k, v := range x {
m2[k.(string)] = convert(v) // 递归转换 value
}
return m2
case []interface{}:
// 遍历切片,递归转换每个元素
for i, v := range x {
x[i] = convert(v)
}
return x
default:
return i // string, number, bool, nil 等原生类型直接返回
}
}? 注意事项:
- 该函数仅处理
map[interface{}]interface{}和[]interface{},其他类型(如int,string,bool,nil)原样透传; - 所有 map key 必须能断言为
string(YAML 规范中合法 key 均可被解析为 string,实践中极少例外); - 若 YAML 包含时间、自定义类型等,需额外扩展
switch分支(但本场景聚焦“无结构”动态数据,通常无需); -
JSON 不保证字段顺序:Go 的
map[string]interface{}是无序的,转换后 JSON 对象内字段顺序可能与原始 YAML 不同(符合 JSON 标准,不影响语义)。
完整使用示例:
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v3"
"encoding/json"
)
func main() {
const yamlStr = `Services:
- Orders:
- ID: $save ID1
SupplierOrderCode: $SupplierOrderCode
- ID: $save ID2
SupplierOrderCode: 111111`
var body interface{}
if err := yaml.Unmarshal([]byte(yamlStr), &body); err != nil {
log.Fatal("YAML unmarshal failed:", err)
}
body = convert(body) // ✅ 关键一步:标准化嵌套结构
jsonBytes, err := json.Marshal(body)
if err != nil {
log.Fatal("JSON marshal failed:", err)
}
fmt.Printf("JSON output:\n%s\n", jsonBytes)
}输出结果(格式化后):
{
"Services": [
{
"Orders": [
{ "ID": "$save ID1", "SupplierOrderCode": "$SupplierOrderCode" },
{ "ID": "$save ID2", "SupplierOrderCode": 111111 }
]
}
]
}? 进阶建议:
- 生产环境推荐使用成熟封装库,如
github.com/icza/dyno(作者已开源本文方案),提供dyno.ConvertMapI2MapS()等高性能工具函数; - 如需保留 YAML 键顺序,可改用
map[string]interface{}+ 自定义有序结构(如[]struct{Key string; Value interface{}}),但会显著增加复杂度,一般 JSON 场景无需强求; - 对超大 YAML 文件,注意递归深度和内存占用,必要时可加入深度限制或迭代式遍历优化。
通过该方法,你即可零结构体依赖、安全可靠地完成 YAML → JSON 的动态双向转换,完美适配配置中心、模板引擎、API 网关等灵活数据处理场景。


















