Recursive AI

Read YAML without a struct

All oneyaml Go recipes

When you don’t have a struct for the data, parse it into a Document and read values by JSON Pointer, or walk its nodes. A node also gives you its position and its comments.

GO
package main
 
import (
	"fmt"
	"log"
 
	oneyaml "oneyaml.dev/go"
)
 
func main() {
	doc, err := oneyaml.Parse([]byte(`server:
  host: 0.0.0.0
  port: 8080   # behind the load balancer
features: [auth, metrics]
limits:
  cpu: 0.5
  memory: 512Mi
`))
	if err != nil {
		log.Fatal(err)
	}
 
	// Read one value into a Go type, by JSON Pointer.
	var port int
	if err := doc.Get("/server/port", &port); err != nil {
		log.Fatal(err)
	}
	fmt.Println("port:", port)
 
	// Or walk the nodes: values, comments and positions.
	root := doc.Root()
	server, _ := root.Lookup("server")
	p, _ := server.Lookup("port")
	line, col := p.Position()
	fmt.Printf("port %s at %d:%d, comment %q\n", p.Value(), line, col, p.LineComment())
 
	features, _ := root.Lookup("features")
	for f := range features.Items() {
		fmt.Println("feature:", f.Value())
	}
	limits, _ := root.Lookup("limits")
	for k, v := range limits.Entries() {
		fmt.Printf("limit %s = %s\n", k.Value(), v.Value())
	}
 
	// A missing path is ErrNotFound, never a panic.
	var tls bool
	fmt.Println(doc.Get("/server/tls", &tls))
}
Text
port: 8080
port 8080 at 3:9, comment "# behind the load balancer"
feature: auth
feature: metrics
limit cpu = 0.5
limit memory = 512Mi
oneyaml: get "/server/tls": path not found

Good to know

  • doc.Get(ptr, &v) decodes the node at a JSON Pointer into any Go value, with the same rules as Unmarshal. A missing path returns an error that matches oneyaml.ErrNotFound with errors.Is.
  • Lookup finds a key in a mapping; Find does the same but returns an error. Entries() and Items() are range-over-func iterators, in file order.
  • Position() is the 1-based line and column, Span() the byte range, and HeadComment() and LineComment() the comments above and after the node.
  • To read into plain Go values instead, Unmarshal into any: a mapping becomes map[string]any, a sequence []any, an integer int.

A longer walk-through, with yaml.v2 and v3 compared: Golang: read a YAML file without a struct.