1. Scenario
When using YAML as a configuration file, it is often necessary to parse the configuration when starting a service. If the data structure of a field is modified, the service might panic immediately. Sometimes, we do not want the business to stop at this point and hope to replace it with a default configuration file.
1# cfg.yaml
2language: Golang
3animal: Dog
1type Cfg struct {
2 Language string `json:"language,omitempty"`
3 Animal string `json:"animal,omitempty"`
4}
Under normal circumstances, the YAML file will be parsed successfully as follows:
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("Failed to parse configuration, err: %v", err)
23 }
24 fmt.Println(cfg)
25}
26// {Golang Dog}
When we modify the YAML file, such as changing the language
field to an array:
1# cfg.yaml
2language:
3 - Golang
4animal: Dog
The error message is as follows:
Failed to parse configuration, 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
2. Solution
At this point, we hope to replace the fields that failed to parse with default values. Many unmarshal functions return an error after encountering the first field that fails to parse, and the remaining fields are not parsed. Here is a workaround method, and we hope to receive better suggestions.
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("Failed to parse configuration, err: %v", err)
15 return
16 }
17 // Apply default settings to fields that failed to parse
18 for key, value := range defaultSettings {
19 // Add missing fields and use default values, and check if the type of the parsed field is consistent with the default setting
20 if fieldValue, ok := tmpCfg[key]; !ok || reflect.TypeOf(fieldValue) != reflect.TypeOf(value) {
21 tmpCfg[key] = value
22 }
23 }
24 // Convert the parsed result to the Cfg structure
25 cfg = Cfg{
26 Language: tmpCfg["language"].(string),
27 Animal: tmpCfg["animal"].(string),
28 }
29 }
30 fmt.Println(cfg)
31}
32// {Golang Dog}