---
title: "Getting started with oneyaml for Rust"
url: https://recursiveai.org/oneyaml/rust/docs/getting-started/
description: "Add the crate, read YAML into Rust types with serde, write it back, edit a document in place and keep its comments, and apply JSON Patch and merge patches."
version: "1.2.0"
---

# Getting started with oneyaml for Rust

Add the oneyaml crate, read YAML into Rust types with serde, write it back, edit a file in place without losing its comments, and apply JSON Patch and merge patches.

oneyaml is serde and lossless, comment-preserving editing for YAML 1.2, in one crate. Both run on one parser, so a typed read and an edit never disagree about what a file means. Every sample on this page was compiled and run against oneyaml 1.2.0 from crates.io; the output under each one is what it printed.

## Install

oneyaml needs Rust 1.85 or newer (edition 2024).

```sh
cargo new yaml-start
cd yaml-start
cargo add oneyaml@1.2.0 serde -F serde/derive
```

The only runtime dependency by default is `serde`. The crate has no `unsafe` code (`#![forbid(unsafe_code)]`), and every fallible call returns a `Result` or an `Option` instead of panicking. The license is MIT or Apache-2.0, at your choice.

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

## Read YAML into a struct

Derive `Deserialize` and call `oneyaml::from_str`. Fields bind by name, the way they do with `serde_json`.

```rust
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Config {
    name: String,
    replicas: u32,
    ports: Vec<u16>,
    debug: bool,
}

fn main() -> Result<(), oneyaml::Error> {
    let src = "\
name: api
replicas: 2
ports: [80, 443]
debug: false
";
    let cfg: Config = oneyaml::from_str(src)?;
    println!(
        "{} runs {} replicas on {:?} (debug: {})",
        cfg.name, cfg.replicas, cfg.ports, cfg.debug
    );
    Ok(())
}
```

```text
api runs 2 replicas on [80, 443] (debug: false)
```

YAML 1.2 is the default: `yes` and `no` are strings, not booleans, and `0755` is the decimal 755. A field of the wrong type is an error with a code, a line and a column, and the path to the value (see [errors with line and column](https://recursiveai.org/oneyaml/rust/cookbook/errors-with-line-and-column/)).

## Write YAML

`oneyaml::to_string` writes any `Serialize` value as block-style YAML.

```rust
use serde::Serialize;
use std::collections::BTreeMap;

#[derive(Serialize)]
struct Config {
    name: String,
    replicas: u32,
    ports: Vec<u16>,
    labels: BTreeMap<String, String>,
}

fn main() -> Result<(), oneyaml::Error> {
    let cfg = Config {
        name: "api".into(),
        replicas: 3,
        ports: vec![80, 443],
        labels: BTreeMap::from([("tier".into(), "web".into())]),
    };
    print!("{}", oneyaml::to_string(&cfg)?);
    Ok(())
}
```

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

`to_writer` writes to any `io::Write`, and `to_string_with` takes `Settings` for the indent and other output choices.

## Edit a file and keep its comments

To change a file that people also edit by hand, parse it into a `Document` instead. An untouched document writes back byte for byte; an edit changes only what it touches, and every comment stays with the entry it belongs to.

```rust
use oneyaml::Document;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = "\
# Deploy settings
name: api
replicas: 2   # raised by the release job
ports: [80]
";
    let mut doc = Document::parse(src)?;

    // Read a value.
    let replicas = doc.get_path("replicas").and_then(|n| n.as_u64());
    println!("replicas = {replicas:?}");

    // Change a value, append to a list, add a key.
    doc.set_path("replicas", 3)?;
    doc.get_mut("ports")?.push(443)?;
    doc.root_mut()?.insert("region", "us-east-1")?;

    print!("{doc}");
    Ok(())
}
```

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

Paths are strings such as `"server.port"` or `"spec.containers[0].image"`. `set_path` keeps the old value’s style: a quoted string stays quoted, a block scalar stays a block scalar. New content is laid out like its neighbors, so the flow list `[80]` stays a flow list. The rules for which entry a comment belongs to are in [comment attachment](https://recursiveai.org/oneyaml/rust/docs/comment-attachment/).

## Apply a JSON Patch or a merge patch

A `Document` applies JSON Patch (RFC 6902) and merge patches (RFC 7396). Both are all or nothing: if any operation fails, the document is exactly as it was. Like the setters, a patch changes only the text of the values it names.

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = "\
# Web service
image: web:1.0   # pinned
env:
  DEBUG: \"1\"
  LOG: info
ports: [80]
";
    // JSON Patch (RFC 6902): all or nothing.
    let mut doc = Document::parse(src)?;
    let patch = Patch::from_json(
        r#"[{"op": "replace", "path": "/image", "value": "web:1.1"},
            {"op": "add", "path": "/ports/-", "value": 443}]"#,
    )?;
    doc.apply_patch(&patch)?;
    print!("{doc}");
    println!("---");

    // Merge patch (RFC 7396): null removes a key.
    let mut doc = Document::parse(src)?;
    let merge = MergePatch::from_json(r#"{"env": {"DEBUG": null, "LOG": "warn"}}"#)?;
    doc.apply_merge_patch(&merge)?;
    print!("{doc}");
    Ok(())
}
```

```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 work on the YAML data model: `test` compares YAML values, so `1` is not `1.0`, and writes below an alias are refused. A `move` carries the value without its comments; to move an entry with them, use `NodeMut::move_entry`. Every difference from JSON is listed in [patches and pointers](https://recursiveai.org/oneyaml/rust/docs/guide/#patches-and-pointers).

## Where next

Recipes, each a complete program:

- [Parse YAML into Rust structs](https://recursiveai.org/oneyaml/rust/cookbook/parse-yaml-into-structs/) and [read YAML without a schema](https://recursiveai.org/oneyaml/rust/cookbook/read-yaml-without-a-schema/)
- [Edit YAML and keep its comments](https://recursiveai.org/oneyaml/rust/cookbook/edit-yaml-keep-comments/) and [merge or patch YAML](https://recursiveai.org/oneyaml/rust/cookbook/patch-yaml/)
- [Convert YAML to JSON and back](https://recursiveai.org/oneyaml/rust/cookbook/yaml-to-json/) and [read Kubernetes manifests and multi-document files](https://recursiveai.org/oneyaml/rust/cookbook/kubernetes-multi-document/)
- [Report errors with line and column](https://recursiveai.org/oneyaml/rust/cookbook/errors-with-line-and-column/), [anchors, aliases and merge keys](https://recursiveai.org/oneyaml/rust/cookbook/anchors-aliases-merge-keys/)
- [Read untrusted YAML safely](https://recursiveai.org/oneyaml/rust/cookbook/untrusted-input/) and [read YAML 1.1 files (`yes`, `no`, `0755`)](https://recursiveai.org/oneyaml/rust/cookbook/yaml-1-1-booleans/)

The docs:

- The [crate guide](https://recursiveai.org/oneyaml/rust/docs/guide/): diagnostics, patches and pointers, untrusted input, merge keys, feature flags. The API reference with an example for every task is on [docs.rs](https://docs.rs/oneyaml/1.2.0/oneyaml/).
- Moving from `serde_yaml`, `serde_yaml_ng` or `serde_norway`: [the migration guide](https://recursiveai.org/oneyaml/rust/docs/migrating-from-serde-yaml/).
- Choosing a crate: [how oneyaml compares](https://recursiveai.org/oneyaml/rust/docs/comparison/), [performance](https://recursiveai.org/oneyaml/rust/docs/performance/) and [yaml-test-suite conformance](https://recursiveai.org/oneyaml/rust/docs/conformance/).
- What changed: the [changelog](https://recursiveai.org/oneyaml/rust/docs/changelog/).

Choosing a Rust YAML crate after serde_yaml: [Rust YAML parser](https://recursiveai.org/oneyaml/articles/rust-yaml-parser/).
