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

# Merge or patch YAML

Overlay one YAML or JSON 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.

```rust
use oneyaml::{Document, MergePatch, Patch, PatchErrorKind};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = "\
# values.yaml
image:
  repository: registry.example.com/web
  tag: \"1.4.2\"   # set by CI
replicas: 2
resources:
  limits:
    cpu: 500m
    memory: 256Mi
";
    // An environment overlay as a merge patch: objects merge, null deletes.
    let mut doc = Document::parse(src)?;
    let overlay = MergePatch::from_json(r#"{"replicas": 5, "resources": {"limits": {"cpu": null, "memory": "1Gi"}}}"#)?;
    doc.apply_merge_patch(&overlay)?;
    print!("{doc}");

    // A JSON Patch with a guard: `test` must pass or nothing changes.
    let bump = Patch::from_json(
        r#"[{"op": "test", "path": "/image/tag", "value": "1.4.2"},
            {"op": "replace", "path": "/image/tag", "value": "1.5.0"}]"#,
    )?;
    doc.apply_patch(&bump)?;
    println!("tag now {:?}", doc.get_path("image.tag").and_then(|n| n.as_str()));

    // Applied again, the test fails, and the document is left as it was.
    let err = doc.apply_patch(&bump).err().ok_or("expected the test to fail")?;
    println!("second run: {:?} at op {:?}", err.kind() == PatchErrorKind::Test, err.op_index());
    Ok(())
}
```

```text
# values.yaml
image:
  repository: registry.example.com/web
  tag: "1.4.2"   # set by CI
replicas: 5
resources:
  limits:
    memory: 1Gi
tag now Some("1.5.0")
second run: true at op Some(0)
```

## Good to know

- **All or nothing.** If any operation fails, the document is exactly as it was. `err.kind()` says why, and `err.op_index()` which 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 (`1` is not `1.0`), a pointer token matches a key by its text, and writes below an alias or to a key merged in by `<<` are refused. A `copy` or `move` writes the value without its comments.
- `Patch::from_json` and `MergePatch::from_json` also read YAML text. For text from outside, read a `Value` with `Limits::untrusted()` first and build the patch with `Patch::from_value` or `MergePatch::new`.

Every difference from RFC 6902 is listed under [patches and pointers](https://recursiveai.org/oneyaml/rust/docs/guide/#patches-and-pointers).

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