为什么有这篇文章
你是不是有过这种烦恼? 自己写的配置yaml文件要把配置信息弄到go的结构体里, 需要我们一个一个把go的结构体代码敲出来, 过程很繁琐, 很影响开发的效率和开发的热情.
现在我将介绍如何编写一个将yaml文件转成go结构体的脚本,这样能很方便地生成go结构, 提升开发效率
实操代码
实现一个yaml类, 方便我们后续调用以及操作, struct字段存储每个生成好的结构体
type Yaml struct {
Struct []string
}
func NewYaml() *Yaml {
return &Yaml{}
}
GetStruct函数
用户调用yaml对象的GetStruct方法, 它将读取文件路径下的yaml文件, 并将其转换成map[string]interface{}类型, 递归调用generateStructCode, 层级生成每一个结构体, 最后在将其写入文件
GetStruct 读取 YAML 文件并生成相应的 Go 结构体
func (y *Yaml) GetStruct(filepath string) (err error) {
data, err := os.ReadFile(filepath)
if err != nil {
return
}
var temp map[string]interface{}
err = yaml.Unmarshal(data, &temp)
if err != nil {
return
}
// 生成结构体
y.Struct = append(y.Struct, y.generateStructCode("config", temp))
create, err := os.Create("./struct_gen.go")
if err != nil {
return err
}
defer create.Close()
_, err = create.Write([]byte("package code_gen\n\n" + strings.Join(y.Struct, "\n")))
y.Struct = []string{}
return
}
generateStructCode函数
这是程序的主要函数, 通过遍历每一个键值对(map), 使用determineType函数获取对应的go的类型, 如果该类型为map[string]interface{}, 说明该函数为嵌套的结构体, 应该递归去生成, 如果 为[]interface{}, 则说明是数组类型. toCamelCase将yaml下划线的命名规范改成驼峰式
// generateStructCode 递归生成结构体代码
func (y *Yaml) generateStructCode(structName string, yamlMap map[string]interface{}) string {
var structFields []string
for key, value := range yamlMap {
fieldName := toCamelCase(key)
fieldType := determineType(value)
if nestedMap, ok := value.(map[string]interface{}); ok {
nestedStructName := fieldName
nestedStruct := y.generateStructCode(nestedStructName, nestedMap)
y.Struct = append(y.Struct, nestedStruct)
fieldType = nestedStructName
} else if array, ok := value.([]interface{}); ok {
// Determine the element type of the array
if len(array) > 0 {
elemType := determineType(array[0])
fieldType = "[]" + elemType
} else {
fieldType = "[]interface{}"
}
}
structFields = append(structFields, fmt.Sprintf("\t%s %s `yaml:"%s"`", fieldName, fieldType, key))
}
return fmt.Sprintf("type %s struct {\n%s\n}\n", structName, strings.Join(structFields, "\n"))
}
determineType函数
// determineType 根据 YAML 数据类型确定 Go 类型
func determineType(value interface{}) string {
switch value.(type) {
case string:
return "string"
case int, int64, int32:
return "int"
case float64, float32:
return "float64"
case bool:
return "bool"
case []interface{}:
return "[]interface{}"
case map[interface{}]interface{}:
return "struct"
default:
return "interface{}"
}
}
toCamelCase函数
// toCamelCase 将 YAML 字段名称转换为驼峰式
func toCamelCase(s string) string {
parts := strings.Split(s, "_")
for i := range parts {
parts[i] = strings.Title(parts[i])
}
return strings.Join(parts, "")
}
测试代码
package code_gen
import (
"testing"
)
func TestYamlToStruct(t *testing.T) {
n := NewYaml()
err := n.GetStruct("./test.yaml")
if err != nil {
t.Error(err)
}
}
结果展示
测试文件
Mysql:
DataSource: root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
Host: 127.0.0.1:6379
Type: node
Pass: test
JWT:
AccessSecret: "test"
AccessExpire: 604800
生成的go文件
package code_gen
type Mysql struct {
DataSource string `yaml:"DataSource"`
}
type CacheRedis struct {
Pass string `yaml:"Pass"`
Host string `yaml:"Host"`
Type string `yaml:"Type"`
}
type JWT struct {
AccessSecret string `yaml:"AccessSecret"`
AccessExpire int `yaml:"AccessExpire"`
}
type config struct {
Mysql Mysql `yaml:"Mysql"`
CacheRedis CacheRedis `yaml:"CacheRedis"`
JWT JWT `yaml:"JWT"`
}