---
title: "Getting started with oneyaml for Go"
url: https://recursiveai.org/oneyaml/go/docs/getting-started/
description: "Install the module, read YAML into Go structs, write it back, edit a document in place and keep its comments, and apply JSON Patch and merge patches."
version: "1.0.0"
---

# Getting started with oneyaml for Go

Install oneyaml for Go, read YAML into Go structs, write it back, edit a file in place without losing its comments, and apply JSON Patch and merge patches.

oneyaml-go is pure-Go YAML 1.2 that edits a file and keeps its comments. Its `Marshal` and `Unmarshal` work the way `encoding/json` does, and a `Document` changes only the values you set. Every sample on this page was compiled and run against `oneyaml.dev/go` v1.0.0; the output under each one is what it printed.

## Install

oneyaml needs Go 1.24 or newer.

```sh
mkdir yaml-start && cd yaml-start
go mod init example.com/yaml-start
go get oneyaml.dev/go@v1.0.0
```

The import path is `oneyaml.dev/go` and the package name is `oneyaml`. It has no dependencies outside the standard library, no `unsafe` and no cgo. The license is MIT or Apache-2.0, at your choice.

The samples are complete `main.go` files. A few read a file from the current directory; the YAML they read is shown with them.

## Read YAML into a struct

`oneyaml.Unmarshal` fills a struct by its `yaml` tags, the way `encoding/json` does with `json` tags.

```go
package main

import (
	"fmt"
	"log"

	oneyaml "oneyaml.dev/go"
)

type Config struct {
	Name     string `yaml:"name"`
	Replicas int    `yaml:"replicas"`
	Ports    []int  `yaml:"ports"`
	Debug    bool   `yaml:"debug"`
}

func main() {
	src := []byte(`name: api
replicas: 2
ports: [80, 443]
debug: false
`)
	var cfg Config
	if err := oneyaml.Unmarshal(src, &cfg); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", cfg)
}
```

```text
{Name:api Replicas:2 Ports:[80 443] Debug:false}
```

YAML 1.2 is the default: plain `yes` is a string, plain `0755` is the integer 755, and quoted `"0755"` is a string. A value that doesn’t fit its field is a `*TypeError` with a path, a line and a column, and the rest of the document is still read (see [errors with line and column](https://recursiveai.org/oneyaml/go/cookbook/errors-with-line-and-column/)).

## Write YAML

`oneyaml.Marshal` writes any Go value as block-style YAML.

```go
package main

import (
	"fmt"
	"log"

	oneyaml "oneyaml.dev/go"
)

type Config struct {
	Name     string            `yaml:"name"`
	Replicas int               `yaml:"replicas"`
	Ports    []int             `yaml:"ports"`
	Labels   map[string]string `yaml:"labels,omitempty"`
}

func main() {
	cfg := Config{
		Name:     "api",
		Replicas: 3,
		Ports:    []int{80, 443},
		Labels:   map[string]string{"tier": "web"},
	}
	out, err := oneyaml.Marshal(cfg)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(string(out))
}
```

```text
name: api
replicas: 3
ports:
  - 80
  - 443
labels:
  tier: web
```

`NewEncoder` writes to an `io.Writer`, one document per `Encode`, and `Indent(n)` sets the indent.

## Edit a file and keep its comments

To change a file that people also edit by hand, parse it into a `Document` instead. `Set` rewrites one value in its own style; comments and formatting elsewhere are kept byte for byte.

```go
package main

import (
	"fmt"
	"log"

	oneyaml "oneyaml.dev/go"
)

func main() {
	src := []byte(`# Deploy settings
name: api
replicas: 2   # raised by the release job
ports: [80]
`)
	doc, err := oneyaml.Parse(src)
	if err != nil {
		log.Fatal(err)
	}

	// Read a value by JSON Pointer.
	var replicas int
	if err := doc.Get("/replicas", &replicas); err != nil {
		log.Fatal(err)
	}
	fmt.Println("replicas =", replicas)

	// Change a value, append to a list, add a key.
	for _, step := range []struct {
		ptr string
		v   any
	}{
		{"/replicas", 3},
		{"/ports/-", 443},
		{"/region", "us-east-1"},
	} {
		if err := doc.Set(step.ptr, step.v); err != nil {
			log.Fatal(err)
		}
	}
	fmt.Print(doc)
}
```

```text
replicas = 2
# Deploy settings
name: api
replicas: 3   # raised by the release job
ports: [80, 443]
region: us-east-1
```

Paths are JSON Pointers (RFC 6901): `/replicas`, `/spec/containers/0/image`, and `/ports/-` for the end of a list. A value that is replaced keeps its style when it can hold the new value; a new member copies the style of its siblings, so the flow list `[80]` stays a flow list. `Remove`, `Rename`, `SetHeadComment` and `SetLineComment` do the rest ([Editing](https://recursiveai.org/oneyaml/go/docs/editing/)).

## Apply a JSON Patch or a merge patch

A `Document` applies RFC 6902 JSON Patch and RFC 7396 merge patches. A patch applies atomically and keeps the file’s layout.

```go
package main

import (
	"fmt"
	"log"

	oneyaml "oneyaml.dev/go"
)

const src = `# Web service
image: web:1.0   # pinned
env:
  DEBUG: "1"
  LOG: info
ports: [80]
`

func main() {
	// JSON Patch (RFC 6902): all or nothing.
	doc, err := oneyaml.Parse([]byte(src))
	if err != nil {
		log.Fatal(err)
	}
	err = doc.ApplyPatch([]byte(`[
		{"op": "replace", "path": "/image", "value": "web:1.1"},
		{"op": "add", "path": "/ports/-", "value": 443}
	]`))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Print(doc)
	fmt.Println("---")

	// Merge patch (RFC 7396): null removes a key.
	doc, err = oneyaml.Parse([]byte(src))
	if err != nil {
		log.Fatal(err)
	}
	merge := []byte(`{"env": {"DEBUG": null, "LOG": "warn"}}`)
	if err := doc.ApplyMergePatch(merge); err != nil {
		log.Fatal(err)
	}
	fmt.Print(doc)
}
```

```text
# Web service
image: web:1.1   # pinned
env:
  DEBUG: "1"
  LOG: info
ports: [80, 443]
---
# Web service
image: web:1.0   # pinned
env:
  LOG: warn
ports: [80]
```

Patches use YAML’s data model: `test` compares YAML values, so the integer `1` and the float `1.0` differ, and a write through an alias is refused. `Apply` takes the same operations built in Go with `PatchTest`, `PatchReplace` and the others ([Patches](https://recursiveai.org/oneyaml/go/docs/patches/)).

## Where next

Recipes, each a complete program:

- [Parse YAML into Go structs](https://recursiveai.org/oneyaml/go/cookbook/parse-yaml-into-structs/) and [read YAML without a struct](https://recursiveai.org/oneyaml/go/cookbook/read-yaml-without-a-struct/)
- [Edit YAML and keep its comments](https://recursiveai.org/oneyaml/go/cookbook/edit-yaml-keep-comments/) and [merge or patch YAML](https://recursiveai.org/oneyaml/go/cookbook/patch-yaml/)
- [Convert YAML to JSON and back](https://recursiveai.org/oneyaml/go/cookbook/yaml-to-json/) and [read Kubernetes manifests and multi-document files](https://recursiveai.org/oneyaml/go/cookbook/kubernetes-multi-document/)
- [Report errors with line and column](https://recursiveai.org/oneyaml/go/cookbook/errors-with-line-and-column/), [anchors, aliases and merge keys](https://recursiveai.org/oneyaml/go/cookbook/anchors-aliases-merge-keys/)
- [Read untrusted YAML safely](https://recursiveai.org/oneyaml/go/cookbook/untrusted-input/) and [switch from yaml.v3 with one import](https://recursiveai.org/oneyaml/go/cookbook/switch-from-yaml-v3/)

The docs:

- [Editing](https://recursiveai.org/oneyaml/go/docs/editing/), [patches](https://recursiveai.org/oneyaml/go/docs/patches/) and [limits](https://recursiveai.org/oneyaml/go/docs/limits/). The API reference with a runnable example for every feature is on [pkg.go.dev](https://pkg.go.dev/oneyaml.dev/go@v1.0.0).
- Moving from another library: [yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/) and [sigs.k8s.io/yaml](https://recursiveai.org/oneyaml/go/docs/migrate-sigs-k8s-yaml/).
- Choosing a library: [how it compares](https://recursiveai.org/oneyaml/go/docs/comparison/), [performance](https://recursiveai.org/oneyaml/go/docs/performance/) and [conformance](https://recursiveai.org/oneyaml/go/docs/conformance/).
- What changed and what stays stable: the [changelog](https://recursiveai.org/oneyaml/go/docs/changelog/) and [stability](https://recursiveai.org/oneyaml/go/docs/overview/#stability).

Choosing a Go YAML library, with four libraries compared on the same file: [Golang YAML](https://recursiveai.org/oneyaml/articles/golang-yaml/).
