YAML in Go: Difference between revisions
Line 101: | Line 101: | ||
) | ) | ||
type | type SomeStruct struct { | ||
Color string `yaml:"color"` | Color string `yaml:"color"` | ||
} | } | ||
Line 109: | Line 109: | ||
color: "blue" | color: "blue" | ||
` | ` | ||
s := | s := SomeStruct{} | ||
err := yaml.NewDecoder(strings.NewReader(yamlStr)).Decode(&s) | err := yaml.NewDecoder(strings.NewReader(yamlStr)).Decode(&s) | ||
if err != nil { | if err != nil { |
Revision as of 22:50, 14 March 2024
External
Internal
Overview
Declare a recursive structure that matches the structure of the YAML file, and then use a YAML encoder/decoder to marshall/unmarshall data in and out.
YAML support is available in the gopkg.in/yaml.v3
package.
If the structure fields are not capitalized, they are not visible across packages, and their content will zero-ed. The structure field will be accessible, but its content will be the corresponding zero value for the type.
It seems that if the structure fields are not capitalized, they are not unmarshalled properly, even if they are used outside of the package. It is probably becauseyaml.NewDecoder().Decode()
cannot see them.
Example
package main
import (
"fmt"
"gopkg.in/yaml.v3"
"os"
)
//
// color: "blue:
// details:
// size: 10
// weight 2.2
// used: true
// options:
// - light
// - medium
// - heavy
//
// Note: if the package name is "config", name the structure differently, config.Config does not work well
type Config struct {
Color string `yaml:"color"`
Details Details `yaml:"details"`
}
type Details struct {
Size int `yaml:"size"`
Weight float64 `yaml:"weight"`
Used bool `yaml:"used"`
Options []string `yaml:"options"`
}
func main() {
config := Config{
Color: "blue",
Details: Details{
Size: 10,
Weight: 2.2,
Used: true,
Options: []string{"light", "medium", "heavy"},
},
}
f, err := os.Create("/Users/ovidiu/tmp/test.yaml")
if err != nil { ... }
//
// Marshal recursive memory struct into a file
//
if err := yaml.NewEncoder(f).Encode(&config); err != nil { ... }
if err = f.Close(); err != nil { ... }
fmt.Printf("yaml file written and closed\n")
f, err = os.Open("/Users/ovidiu/tmp/test.yaml")
if err != nil { ... }
defer func() {
if err = f.Close(); err != nil { ... }
}()
//
// Unmarshall the file into a different memory struct
//
config2 := Config{}
err = yaml.NewDecoder(f).Decode(&config2)
// the decoder return io.EOF on an empty YAML file, even if the file contains
// comments. We don't want to handle this as error, but as simply "empty file"
if errors.Is(err, io.EOF) {
// nothing to do, the config is empty anyway
log.Printf("empty configuration\n")
} else {
// handle as error
...
}
fmt.Printf("%+v\n", config2)
}
Simpler Example for Automatic Unmarshalling
Annotating the structure fields with `yaml:"someField"`
enables automatic unmarshalling: if the YAML field name coincides with the value passed as `yaml:`, the field will be parsed and its value assigned to the structure field.
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
)
type SomeStruct struct {
Color string `yaml:"color"`
}
func main() {
yamlStr := `
color: "blue"
`
s := SomeStruct{}
err := yaml.NewDecoder(strings.NewReader(yamlStr)).Decode(&s)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", s.Color)
}
Custom Unmarshalling
Some times we need to turn off automatic unmarshalling and handle unmarshalling by ourselves. This is achieved by associating a UnmarshalYAML(unmarshal func(interface{}) error)
with the struct type we want to unmarshall into. The method will be called by the decoder and it will be passed an unmarshal(interface{})
function as argument. Invoking the unmarshal()
on a pointer to a map[string]interface{}
will fill the map with the parsed elements of the YAML document.
type SomeStruct struct {
Color string `yaml:"color"`
}
func (s *SomeStruct) UnmarshalYAML(unmarshal func(interface{}) error) error {
m := map[string]interface{}{}
err := unmarshal(&m)
if err != nil {
return err
}
color, exists := m["color"]
if !exists {
s.Color = "RED" // this is how we assign a default value
} else {
s.Color = color.(string)
}
return nil
}
Behavior when an Entire Subtree Is Missing
If a YAML node (subtree) is missing, the corresponding struct
field contains the struct zero value for the corresponding struct type, so it is safe to access it. Just be mindful you will get the zero values, recursively.