Recursive AI

Merge or patch YAML

All oneyaml Go recipes

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.

A longer guide: Apply a JSON Patch to a YAML file.