A Go library that extends YAML with cross-file references, allowing you to split large configuration files into smaller, reusable pieces.
- Reference YAML anchors across multiple files using the
!xreftag - Support for standard YAML map merges with cross-file references
- Streaming
DecoderAPI with options likeKnownFields - Load files individually, by directory, or recursively
- Built on top of
go.yaml.in/yaml/v3 - Circular dependency detection
go get github.com/supakeen/yamlplusGiven two YAML files:
database.yaml:
connection: &db-config
host: localhost
port: 5432
timeout: 30sapp.yaml:
service:
name: myapp
database: !xref "database.yaml#db-config"Load and unmarshal them:
package main
import (
"fmt"
"os"
"github.com/supakeen/yamlplus"
)
func main() {
loader := yamlplus.NewLoader(os.DirFS("config"))
loader.RegisterFile("database.yaml")
var config map[string]any
data, _ := os.ReadFile("config/app.yaml")
loader.Unmarshal(data, &config)
// Access the cross-referenced database configuration
service := config["service"].(map[string]any)
db := service["database"].(map[string]any)
fmt.Printf("Database: %s:%d\n", db["host"], db["port"])
}For streaming decoding or when you need options like strict field checking, use
NewDecoder instead of Unmarshal:
file, _ := os.Open("config/app.yaml")
defer file.Close()
dec := loader.NewDecoder(file)
dec.KnownFields(true) // error on unknown fields
var config AppConfig
if err := dec.Decode(&config); err != nil {
log.Fatal(err)
}The Decoder supports all the same !xref resolution as Unmarshal. It also
handles multi-document YAML streams — call Decode repeatedly until it returns
io.EOF:
dec := loader.NewDecoder(file)
for {
var doc map[string]any
if err := dec.Decode(&doc); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
fmt.Println(doc)
}The !xref tag supports two forms:
config: !xref "filename.yaml#anchorname"config: !xref "filename.yaml"When referencing a file without an anchor, the first document in the file is used.
Cross-file references work with YAML's map merge syntax:
defaults.yaml:
defaults: &api-defaults
timeout: 30s
retries: 3
log_level: infoservice.yaml:
production:
<<: !xref "defaults.yaml#api-defaults"
timeout: 60s # Override the default
endpoint: /api/v1Result after unmarshaling:
production:
timeout: 60s
retries: 3
log_level: info
endpoint: /api/v1loader := yamlplus.NewLoader(os.DirFS("config"))
loader.RegisterFile("base.yaml")
loader.RegisterFile("database.yaml")loader.RegisterDirectory("configs")This loads all .yaml and .yml files in the directory (non-recursive).
loader.RegisterRecursively("configs")This walks the directory tree and loads all YAML files.
Files are registered and referenced using their exact path relative to the filesystem root:
loader := yamlplus.NewLoader(os.DirFS("/etc"))
loader.RegisterFile("app/config.yaml")
// Must use the same path in references:
data := []byte(`settings: !xref "app/config.yaml"`)The library detects circular references and returns an error:
a.yaml:
value: !xref "b.yaml"b.yaml:
value: !xref "a.yaml"Attempting to unmarshal will result in an error: circular dependency detected.
A Loader is safe for concurrent Unmarshal calls after all files have been registered. However, RegisterFile, RegisterDirectory, and RegisterRecursively should not be called concurrently with each other or with Unmarshal.
A command-line tool to resolve all !xref tags in a YAML file and output plain YAML.
go install github.com/supakeen/yamlplus/cmd/yamlplus-flatten@latestyamlplus-flatten [flags] <input.yaml>Flags:
| Long | Short | Description |
|---|---|---|
--register-file PATH |
-f PATH |
Register a single YAML file (repeatable) |
--register-directory PATH |
-d PATH |
Register all YAML files in a directory (repeatable) |
--register-recursively PATH |
-r PATH |
Register all YAML files in a directory tree (repeatable) |
yamlplus-flatten -f defaults.yaml -f database.yaml service.yamlThis registers defaults.yaml and database.yaml, then resolves all !xref tags in service.yaml and prints the flattened result to stdout.
See the examples in the documentation for more usage patterns.
go test ./...MIT