Recursive AI

Parse YAML into Go structs

All oneyaml Go recipes

Read a YAML file into typed Go data: nested structs, a map, an optional section, durations and defaults. oneyaml.Unmarshal works like encoding/json’s, with yaml struct tags.

The file:

YAML
# service.yaml
name: web
image: nginx:1.27
timeout: 30s
env:
  LOG_LEVEL: info
  PORT: "8080"
healthcheck:
  path: /healthz
  interval: 10s

The program:

GO
package main
 
import (
	"fmt"
	"log"
	"os"
	"time"
 
	oneyaml "oneyaml.dev/go"
)
 
type Service struct {
	Name        string            `yaml:"name"`
	Image       string            `yaml:"image"`
	Replicas    int               `yaml:"replicas,omitempty"`
	Timeout     time.Duration     `yaml:"timeout"`
	Env         map[string]string `yaml:"env"`
	Healthcheck *Healthcheck      `yaml:"healthcheck"`
}
 
type Healthcheck struct {
	Path     string        `yaml:"path"`
	Interval time.Duration `yaml:"interval"`
}
 
func main() {
	src, err := os.ReadFile("service.yaml")
	if err != nil {
		log.Fatal(err)
	}
	svc := Service{Replicas: 1} // defaults: a missing key keeps the field as it is
	if err := oneyaml.Unmarshal(src, &svc, oneyaml.KnownFields()); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s runs %s x%d, timeout %v\n", svc.Name, svc.Image, svc.Replicas, svc.Timeout)
	fmt.Println("env:", svc.Env)
	if h := svc.Healthcheck; h != nil {
		fmt.Printf("health check: %s every %v\n", h.Path, h.Interval)
	}
 
	// KnownFields: a key the struct doesn't have is an error, not ignored.
	err = oneyaml.Unmarshal([]byte("name: web\nimgae: nginx\n"), &svc, oneyaml.KnownFields())
	fmt.Println(err)
}
Text
web runs nginx:1.27 x1, timeout 30s
env: map[LOG_LEVEL:info PORT:8080]
health check: /healthz every 10s
oneyaml: line 2, column 1: /imgae: field "imgae" not found in type main.Service

Good to know

  • Defaults: set them on the value before Unmarshal. A key that isn’t in the file leaves its field as it was, so Replicas stays 1. A null value also leaves a struct field, number or string as it was.
  • KnownFields() makes a key the struct doesn’t have an error that names it, with its position. Without it, unknown keys are ignored.
  • time.Duration reads its String form (30s, 1m30s), time.Time a timestamp, and *big.Int any integer.
  • PORT: "8080" is quoted, so it is a string. Plain 8080 is an integer; reading it into a string field is a *TypeError, not a silent conversion.
  • Types with an UnmarshalYAML method, encoding.TextUnmarshaler or json.Unmarshaler read themselves, so types written for encoding/json (Kubernetes quantities, say) need no YAML code.

When the shape isn’t known ahead of time, read nodes from a Document: read YAML without a struct.