场景

当 Yaml 作为配置文件时候,往往在启动服务的时候就需要解析配置,如果修改了某个字段的数据结构,开局就 panic,有时候我们并不希望此时就停止业务,希望能用默认的配置文件替代。

1# cfg.yaml
2language: Golang
3animal: Dog
1type Cfg struct {
2	Language    string `json:"language,omitempty"`
3	Animal 		string `json:"animal,omitempty"`
4}

正常情况下,Yaml 文件会被解析成功,如下:

 1package main
 2
 3import (
 4	"fmt"
 5	"os"
 6
 7	"github.com/goccy/go-yaml"
 8)
 9
10type Cfg struct {
11	Language string `json:"language,omitempty"`
12	Animal   string `json:"animal,omitempty"`
13}
14
15func main() {
16	CfgPath := "C:\\Users\\han1en9\\Desktop\\Project\\Demo\\cfg.yaml"
17
18	var cfg Cfg
19	b, _ := os.ReadFile(CfgPath)
20
21	if err := yaml.Unmarshal(b, &cfg); err != nil {
22		fmt.Printf("解析配置失败, err : %v", err)
23	}
24	fmt.Println(cfg)
25}
26// {Golang Dog}

当我们修改 Yaml 文件时,如修改 language 字段为数组:

1# cfg.yaml
2language:
3  - Golang
4animal: Dog

报错信息如下:

解析配置失败, err : [4:3] cannot unmarshal []interface {} into Go struct field Cfg.Language of type string 1 | # cfg.yaml 2 | language: 3 | - Golang ^ { Dog} 5 | animal: Dog

解决方法

此时我们希望解析错误的字段用默认值代替,很多 unmarshal 函数在解析出错的第一个字段后就返回错误,剩余字段不再去解析,想了一个曲线救国的方法,希望大家有更好的建议。

 1func main() {
 2	CfgPath := "C:\\Users\\han1en9\\Desktop\\Project\\Demo\\cfg.yaml"
 3
 4	var cfg Cfg
 5	b, _ := os.ReadFile(CfgPath)
 6
 7	if err := yaml.Unmarshal(b, &cfg); err != nil {
 8		defaultSettings := map[string]interface{}{
 9			"language": "Golang",
10			"animal":   "Dog",
11		}
12		tmpCfg := make(map[string]interface{})
13		if err = yaml.Unmarshal(b, &tmpCfg); err != nil {
14			fmt.Printf("解析配置失败, err : %v", err)
15			return
16		}
17		// 应用默认设置到解析失败的字段
18		for key, value := range defaultSettings {
19			// 添加缺失的字段并使用默认值并检查解析字段的类型和默认设置的类型是否一致
20			if fieldValue, ok := tmpCfg[key]; !ok || reflect.TypeOf(fieldValue) != reflect.TypeOf(value) {
21				tmpCfg[key] = value
22			}
23		}
24		// 将解析结果转换为 Cfg 结构
25		cfg = Cfg{
26			Language: tmpCfg["language"].(string),
27			Animal:   tmpCfg["animal"].(string),
28		}
29	}
30	fmt.Println(cfg)
31}
32// {Golang Dog}