Cookbook
Short, copy-paste recipes for the tasks that come up most. For the basics, read Getting started first.
Snippets elide the enclosing function and use
?; assume each runs in a function returningsaneyaml::Result<()>.
- Multi-document streams
- Work with
Value - Enums and singleton maps
- Anchors and merge keys
- Numbers, timestamps, and binary
- Control emitted YAML
- Custom tags
Multi-document streams
Kubernetes-style ----separated streams. Get everything at once:
#![allow(unused)]
fn main() {
let stream = "\
kind: Service
---
kind: Deployment
";
let docs: Vec<Manifest> = saneyaml::from_documents_str(stream)?;
assert_eq!(docs.len(), 2);
}
Or process one document at a time — useful when an early document is valid but a later one fails, and you want the good ones first:
#![allow(unused)]
fn main() {
use serde::Deserialize;
for doc in saneyaml::Deserializer::from_str(stream) {
let manifest = Manifest::deserialize(doc)?;
// handle manifest…
}
}
from_documents_str is all-or-error; the iterator yields parsed documents up to
the first error. For bounded memory on large streams, see
Streaming.
Work with Value
Read, mutate, and patch a dynamic document. Indexing returns a null sentinel for missing paths, so chains don’t panic:
#![allow(unused)]
fn main() {
let mut v: saneyaml::Value = saneyaml::from_str("\
services:
api:
image: nginx:1.25
ports: [80]
")?;
// read
assert_eq!(v["services"]["api"]["image"].as_str(), Some("nginx:1.25"));
// patch in place
v["services"]["api"]["image"] = saneyaml::Value::from("nginx:1.27");
// mutate a sequence
if let Some(ports) = v["services"]["api"]["ports"].as_sequence_mut() {
ports.push(saneyaml::Value::from(443));
}
}
Value::from accepts strings, bools, every integer/float width, Mapping, and
Vec<Value>. Build maps with Mapping, which keeps insertion order and offers
the full entry / get / insert / remove API.
To convert between typed values and Value without going through text, use
to_value and from_value:
#![allow(unused)]
fn main() {
let value = saneyaml::to_value(&cfg)?; // T -> Value
let cfg: Config = saneyaml::from_value(value)?; // Value -> T
}
from_valueis spanless: it won’t coerce a number or bool into aStringtarget, because the original spelling is gone after the value is built. Read withfrom_str/from_slicewhen a string field must preserve source text like1_000orFALSE.
Enums and singleton maps
Data-carrying enums round-trip as YAML tags by default (!Variant value). For
the serde_yaml single-key-map shape (variant: value), annotate the field:
#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
enum Action { Run(String), Skip }
#[derive(Serialize, Deserialize)]
struct Job {
#[serde(with = "saneyaml::with::singleton_map")]
action: Action,
}
}
Use saneyaml::with::singleton_map_recursive when nested enum payloads also need
the one-entry-map shape. To emit every enum as a singleton map without
annotating each field, set the emitter option globally:
#![allow(unused)]
fn main() {
use saneyaml::{EmitOptions, EnumRepresentation};
let opts = EmitOptions::structural()
.with_enum_representation(EnumRepresentation::SingletonMap);
let text = saneyaml::to_string_with_options(&job, opts)?;
}
Anchors and merge keys
&anchor / *alias and the << merge key are expanded for you when loading
into Node or Value — you read the effective, merged result:
#![allow(unused)]
fn main() {
let v: saneyaml::Value = saneyaml::from_str("\
defaults: &defaults
retries: 3
service:
<<: *defaults
name: api
")?;
assert_eq!(v["service"]["retries"].as_u64(), Some(3));
assert_eq!(v["service"]["name"].as_str(), Some("api"));
}
Explicit keys override merged ones, and earlier entries in a merge list win.
Value::apply_merge() is also available as an explicit in-place helper.
If you need the raw << syntax and anchor/alias graph identity (not the
expanded result), parse with parse_events / EventStream or
the lossless graph.
Numbers, timestamps, and binary
Number widens to i128 / u128; the usual helpers are range-checked:
#![allow(unused)]
fn main() {
let v: saneyaml::Value = saneyaml::from_str("count: 9000000000000000000\n")?;
assert_eq!(v["count"].as_u64(), Some(9_000_000_000_000_000_000));
}
Timestamps and !!binary are YAML 1.1 features and need an explicit schema or
tag. !!timestamp scalars are read via as_timestamp() / typed
saneyaml::Timestamp fields; !!binary decodes into byte targets like
Vec<u8>. See Schema modes for enabling YAML 1.1 typing.
Control emitted YAML
The default (EmitOptions::structural()) is insertion-order, plain-where-safe,
block layout. Tune it with builder methods:
#![allow(unused)]
fn main() {
use saneyaml::{EmitOptions, KeyOrder, ScalarQuoteStyle};
let opts = EmitOptions::structural()
.with_key_order(KeyOrder::Sort) // Preserve | Sort
.with_scalar_quote_style(ScalarQuoteStyle::DoubleQuoted); // PlainWhereSafe | SingleQuoted | DoubleQuoted
let text = saneyaml::to_string_with_options(&cfg, opts)?;
}
Other knobs: with_collection_style (Block | Flow), with_block_scalar_style
(Literal | Folded), with_enum_representation (Tag | SingletonMap), and
with_yaml_1_1_safe_strings(true) to quote strings like no / 12:34:56 so
older YAML 1.1 readers don’t reinterpret them.
For byte-for-byte serde_yaml writer output on the supported structural corpus,
use EmitOptions::byte_compatible(). Comments and source formatting are not a
writer concern — use in-place editing to preserve them.
Custom tags
Application tags (!Ref, !Env, CloudFormation intrinsics, …) are preserved in
Value and visible to enum dispatch. For ordinary typed reads they’re
transparent metadata:
#![allow(unused)]
fn main() {
// !Env prod -> String "prod"
// !Ports [80, 443] -> Vec<u16>
// !Maybe null -> Option<T> (None)
}
Inspect a tag explicitly with value.as_tagged(), which exposes the tag and the
inner Value.