yaml文件转对应的go结构体

345 阅读2分钟

为什么有这篇文章

你是不是有过这种烦恼? 自己写的配置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"`
}