---
title: "Merge or patch YAML"
url: https://recursiveai.org/oneyaml/go/cookbook/patch-yaml/
description: "Apply a merge patch (RFC 7396) as an environment overlay and a guarded JSON Patch (RFC 6902) built in Go to a YAML file, keeping its comments."
version: "1.0.0"
---

# Merge or patch YAML

Overlay one document on another, or apply a JSON Patch, without losing the file’s comments and layout. A merge patch suits environment overrides; a JSON Patch suits scripted changes with a `test` guard.

```go
package main

import (
	"errors"
	"fmt"
	"log"

	oneyaml "oneyaml.dev/go"
)

func main() {
	doc, err := oneyaml.Parse([]byte(`# values.yaml
image:
  repository: registry.example.com/web
  tag: "1.4.2"   # set by CI
replicas: 2
resources:
  limits:
    cpu: 500m
    memory: 256Mi
`))
	if err != nil {
		log.Fatal(err)
	}

	// An environment overlay as a merge patch: objects merge, null deletes.
	overlay := `{"replicas": 5, "resources": {"limits": {"cpu": null, "memory": "1Gi"}}}`
	if err := doc.ApplyMergePatch([]byte(overlay)); err != nil {
		log.Fatal(err)
	}
	fmt.Print(doc)

	// A JSON Patch with a guard, built in Go: `test` must pass or nothing changes.
	bump := []oneyaml.PatchOp{
		oneyaml.PatchTest("/image/tag", "1.4.2"),
		oneyaml.PatchReplace("/image/tag", "1.5.0"),
	}
	if err := doc.Apply(bump...); err != nil {
		log.Fatal(err)
	}
	var tag string
	_ = doc.Get("/image/tag", &tag)
	fmt.Println("tag now", tag)

	// Applied again, the test fails and the document is left as it was.
	err = doc.Apply(bump...)
	var ee *oneyaml.EditError
	fmt.Println("second run refused:", errors.As(err, &ee), err)
}
```

```text
# values.yaml
image:
  repository: registry.example.com/web
  tag: "1.4.2"   # set by CI
replicas: 5
resources:
  limits:
    memory: 1Gi
tag now 1.5.0
second run refused: true oneyaml: patch operation 0 (test "/image/tag"): patch test failed
```

## Good to know

- **Atomic.** If any operation fails, nothing is applied and the error is an `*EditError` that names the operation.
- **A merge patch merges mappings and replaces everything else.** `null` deletes a key; a list in the patch replaces the whole list.
- **Only the named values change.** The `# set by CI` comment and the quotes around the tag stay.
- **YAML rules, not JSON rules.** `test` compares YAML values, so `1` and `1.0` differ, and a write through an alias is refused.
- `ApplyPatch` takes RFC 6902 JSON text; `Apply` takes `PatchOp` values built with `PatchAdd`, `PatchRemove`, `PatchReplace`, `PatchMove`, `PatchCopy` and `PatchTest`. The work of a patch is bounded unless you pass `NoLimits()`.

Every rule is in [Patches](https://recursiveai.org/oneyaml/go/docs/patches/).

A longer guide: [Apply a JSON Patch to a YAML file](https://recursiveai.org/oneyaml/articles/json-patch-yaml/).
