Recursive AI

Merge or patch YAML

All oneyaml Rust recipes

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.

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