Recursive AI

Choosing a Go YAML library

Golang YAML libraries compared: yaml.v3 is archived and v4 is a release candidate. What go.yaml.in, sigs.k8s.io/yaml, goccy/go-yaml and oneyaml each do.

Choosing a Golang YAML library in 2026 starts with the one most Go code already imports: gopkg.in/yaml.v3 is archived, and its README now opens with “THIS PROJECT IS UNMAINTAINED.” The YAML organization carries it on as go.yaml.in/yaml/v3, with a frozen API and security fixes only, and is building go.yaml.in/yaml/v4, which was still a release candidate on 26 October 2026. Beyond the go-yaml line, sigs.k8s.io/yaml is the reference for Kubernetes-style types with json tags, goccy/go-yaml adds an AST and YAML Path queries, and oneyaml reads YAML 1.2 and edits a file without disturbing its comments.

Every status line below comes from the project’s own README or module listing, read on 26 October 2026. Behavior was measured by running each library’s published version, and the program is on this page so you can run it yourself.

Go YAML libraries at a glance

Module and latest versionStatus, per the projectHow it works
gopkg.in/yaml.v3 v3.0.1 (May 2022)Repository archived; the README says “THIS PROJECT IS UNMAINTAINED”“a pure Go port of the well-known libyaml C library”, with a yaml.Node tree
go.yaml.in/yaml/v3 v3.0.5 (July 2026)“Stable, frozen API; security fixes only” (yaml/go-yaml)The yaml.v3 code under the YAML organization; change the import path and nothing else
go.yaml.in/yaml/v4 v4.0.0-rc.6 (June 2026)“v4.0.0 is not released yet. It is intended to be released in October 2026.” New work happens here“highly compatible with v3”, plus Load and Dump with options and plugins
sigs.k8s.io/yaml v1.6.0 (July 2025)Maintained by Kubernetes SIGs, “a permanent fork of ghodss/yaml” (README)“first converts YAML to JSON using go-yaml and then uses json.Marshal and json.Unmarshal”
github.com/goccy/go-yaml v1.19.2 (January 2026)Maintained (README)Its own parser, written from scratch, with a tokenizer, parser and AST, and YAML Path for filtering and replacing
oneyaml.dev/go v1.0.0 (October 2026)New: 1.0.0 is its first stable releaseIts own parser feeding Marshal/Unmarshal and a lossless Document; YAML 1.2 Core schema; no dependencies, no cgo

Versions and dates are the latest each module proxy listed on 26 October 2026. go-yaml v4 may ship soon after this was written, so check its README before you decide.

The same file, read by four Go YAML libraries

The libraries disagree about what plain YAML means. YAML 1.1 read yes, no, on and off as booleans and a leading zero as octal. YAML 1.2, the current specification, has only true and false as booleans and writes octal as 0o755, so 0755 is the decimal 755. This program reads one three-line file with each library into a map[string]any:

main.goGo 1.26
package main
 
import (
    "fmt"
 
    goccy "github.com/goccy/go-yaml"
    yamlv3 "go.yaml.in/yaml/v3"
    oneyaml "oneyaml.dev/go"
    k8syaml "sigs.k8s.io/yaml"
)
 
const src = "country: no\nenabled: yes\nmode: 0755\n"
 
func main() {
    decoders := []struct {
        name      string
        unmarshal func([]byte, any) error
    }{
        {"go.yaml.in/yaml/v3", yamlv3.Unmarshal},
        {"sigs.k8s.io/yaml", func(b []byte, v any) error { return k8syaml.Unmarshal(b, v) }},
        {"goccy/go-yaml", goccy.Unmarshal},
        {"oneyaml.dev/go", func(b []byte, v any) error { return oneyaml.Unmarshal(b, v) }},
    }
    for _, d := range decoders {
        var m map[string]any
        if err := d.unmarshal([]byte(src), &m); err != nil {
            fmt.Printf("%-20s error: %v\n", d.name, err)
            continue
        }
        fmt.Printf("%-20s country=%#v enabled=%#v mode=%v\n", d.name, m["country"], m["enabled"], m["mode"])
    }
}
Output
go.yaml.in/yaml/v3   country="no" enabled="yes" mode=493
sigs.k8s.io/yaml     country=false enabled=true mode=493
goccy/go-yaml        country="no" enabled="yes" mode=493
oneyaml.dev/go       country="no" enabled="yes" mode=755
  • sigs.k8s.io/yaml turns country: no into false, the “Norway problem”, because it reads YAML 1.1 forms before it hands JSON to encoding/json.
  • yaml.v3 keeps yes and no as strings in an untyped map, but its README says YAML 1.1 booleans “are supported as long as they are being decoded into a typed bool value”, and octals “decode as 0777 per YAML 1.1”. goccy/go-yaml matched it on this file.
  • oneyaml reads the YAML 1.2 Core schema: two strings and the integer 755. A yes read into a bool field is a type error with its line and column, not a silent true.

The versions measured were go.yaml.in/yaml/v3 v3.0.5, sigs.k8s.io/yaml v1.6.0, goccy/go-yaml v1.19.2 and oneyaml.dev/go v1.0.0. If your files were written for YAML 1.1 readers, the difference matters when you switch, in either direction.

Read and edit YAML in Go with oneyaml

oneyaml needs Go 1.24 or newer. The import path is oneyaml.dev/go and the package name is oneyaml:

TerminalGo modules
go get oneyaml.dev/[email protected]

Unmarshal fills a struct by its yaml tags, the way encoding/json does. To change a file that people also edit by hand, parse it into a Document and set values by JSON Pointer:

main.gooneyaml.dev/go v1.0.0
package main
 
import (
    "fmt"
    "log"
 
    oneyaml "oneyaml.dev/go"
)
 
type Config struct {
    Name     string `yaml:"name"`
    Replicas int    `yaml:"replicas"`
    Ports    []int  `yaml:"ports"`
}
 
func main() {
    src := []byte(`# Deploy settings
name: api
replicas: 2   # raised by the release job
ports: [80]
`)
    // Typed read, the way encoding/json works.
    var cfg Config
    if err := oneyaml.Unmarshal(src, &cfg); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v\n", cfg)
 
    // Edit the same text: only the values you set change.
    doc, err := oneyaml.Parse(src)
    if err != nil {
        log.Fatal(err)
    }
    if err := doc.Set("/replicas", 3); err != nil {
        log.Fatal(err)
    }
    if err := doc.Set("/ports/-", 443); err != nil {
        log.Fatal(err)
    }
    fmt.Print(doc)
}
Output
{Name:api Replicas:2 Ports:[80]}
# Deploy settings
name: api
replicas: 3   # raised by the release job
ports: [80, 443]

The comment after replicas and the flow style of [80] stay as they were: Set rewrites one value in its own style, and a new list item copies the style of its siblings. The getting started guide covers writing YAML, patches and errors, and the struct recipe and reading YAML without a struct go further.

What each library is for

yaml.v3 and go.yaml.in/yaml/v3

The incumbent. Most Go code that reads YAML, and many of your dependencies, import it, and its yaml.Node API is documented everywhere. The original module gets no more fixes; go.yaml.in/yaml/v3 is the same API with security fixes, so switching to it is an import-path change. More in yaml.v3 is archived: what to use instead.

go.yaml.in/yaml/v4

The continuation of the yaml.v3 code base, where its maintainers say new features and routine fixes are developed. It keeps Unmarshal and Marshal, adds Load and Dump with options, and ships version presets that restore v3 defaults (README). Until it is released, you are depending on a release candidate.

sigs.k8s.io/yaml

The way Kubernetes binds manifests: YAML becomes JSON, and your types use json tags and MarshalJSON/UnmarshalJSON. If your types are Kubernetes API types, it is the reference for what a manifest means. Because the trip goes through JSON, comments don’t survive it, and its README warns that map keys that are maps are an error.

goccy/go-yaml

A from-scratch parser with an AST you can walk and change, YAML Path expressions for filtering and replacing, and readable error messages. Its README reports passing about 60 more yaml-test-suite cases than yaml.v3 (as of 2024-12-15). Choose it when you want to query a document by path.

oneyaml

YAML 1.2 by default, with Marshal and Unmarshal that work like encoding/json and a Document that changes only the values you set: comments and formatting elsewhere stay byte for byte. It applies RFC 6902 JSON Patch and RFC 7396 merge patches to the YAML text, and bounds nesting, alias expansion and number size by default, with an Untrusted() preset for input from outside (limits). Adapters for yaml.v3 and sigs.k8s.io/yaml keep existing code compiling while you move. Speed and memory against the libraries above are measured on real-world files on the performance page, with the method and machine.

How to choose a Go YAML library

  • Your yaml.v3 code works and you only need a maintained module: change the import to go.yaml.in/yaml/v3.
  • Your types are Kubernetes API types with json tags: sigs.k8s.io/yaml, or oneyaml’s compat/k8syaml adapter if you want the same binding with oneyaml underneath.
  • You query documents by path: goccy/go-yaml. oneyaml’s pointers address one value; it has no query language.
  • You edit YAML files that people maintain, and the diff must hold only your change: oneyaml. See edit YAML without losing comments.
  • You apply JSON Patch or merge patches to YAML: oneyaml applies them to the file itself (JSON Patch for YAML).
  • You read YAML from outside your program: oneyaml with Untrusted(), which also bounds input size.
  • You need years of field use: the go-yaml line has them. oneyaml is new, and its first release, 0.1.0, is from October 2026 (changelog).

For a side-by-side of features, with where the other libraries are the better fit, see how oneyaml compares. All three oneyaml languages are listed on the oneyaml home page.