Recursive AI

serde_yaml is deprecated: what now

serde_yaml is archived and marked deprecated on crates.io. The maintained forks, the one-line Cargo rename, and moving to oneyaml with the same function names.

Yes, serde_yaml is deprecated: its last release is versioned 0.9.34+deprecated, its GitHub repository was archived on 25 March 2024, and its README says “This project is no longer maintained.” Existing code keeps compiling, but no fixes will come. To replace it, either switch to a maintained fork with the same API, such as yaml_serde from the YAML organization, which takes one line in Cargo.toml, or move to a crate like oneyaml, which keeps serde_yaml’s function names and adds YAML 1.2 reading and comment-preserving edits.

The status of serde_yaml and its successors

CrateLatest on crates.io (26 Oct 2026)Status, per its own page
serde_yaml0.9.34+deprecated, 25 Mar 2024Repository archived; README: “(This project is no longer maintained.)” (GitHub)
yaml_serde0.10.7, 18 Aug 2026“the actively maintained fork of serde-yaml … by the official YAML organization” (README)
serde_yaml_ng0.10.0, 26 May 2024A fork of serde_yaml’s last commit that aims to be “compatible as much as possible” and “only follows the YAML specification 1.1” (README)
serde_norway0.9.42, 21 Dec 2024“A hard-fork of Serde YAML” (README)
serde_yml0.0.13, 27 May 2026Its crates.io description: “DEPRECATED — serde_yml is unmaintained”
oneyaml1.2.0, 21 Oct 2026A separate implementation: serde plus a lossless Document, YAML 1.2

If a dependency warning sent you here, note that RustSec’s unmaintained advisory RUSTSEC-2024-0320 is for yaml-rust, a different crate, not for serde_yaml. RustSec’s serde_yaml page lists only a 2018 vulnerability.

Keep the API: rename to a fork

yaml_serde’s README shows how to swap it in with Cargo’s package renaming, so every use serde_yaml:: line stays as it is:

Cargo.tomlyaml_serde 0.10
[dependencies]
serde_yaml = { package = "yaml_serde", version = "0.10" }

We built a program that calls serde_yaml::from_str against this line, and Cargo resolved it to yaml_serde 0.10.7. This keeps your code and the libyaml-based behavior it was written against: in cargo tree, yaml_serde depends on libyaml-rs and serde_yaml_ng on unsafe-libyaml, both described on crates.io as “libyaml transpiled to rust by c2rust”.

Move to oneyaml: the use swap

oneyaml keeps serde_yaml’s entry-point names (from_str, from_slice, from_reader, to_string, to_writer, from_value, to_value, Value, Mapping, Number, Sequence, Error), so most code moves by changing the crate name:

TerminalCargo
cargo remove serde_yaml
cargo add [email protected]

Here the same struct is read by serde_yaml_ng, standing in for serde_yaml with the same 0.9 API, and by oneyaml:

main.rsoneyaml 1.2.0 · serde_yaml_ng 0.10.0
use serde::{Deserialize, Serialize};
 
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Config {
    name: String,
    port: u16,
    tags: Vec<String>,
}
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = "name: web\nport: 8080\ntags:\n- api\n- internal\n";
 
    // Before: serde_yaml (here its fork serde_yaml_ng, the same 0.9 API).
    let before: Config = serde_yaml_ng::from_str(src)?;
 
    // After: the same calls with the crate name swapped.
    let after: Config = oneyaml::from_str(src)?;
    assert_eq!(before, after);
 
    // oneyaml's own layout indents a sequence under its key ...
    print!("{}", oneyaml::to_string(&after)?);
    println!("---");
    // ... and Compat::serde_yaml() writes what serde_yaml wrote.
    let compat = oneyaml::to_string_with(&after, &oneyaml::Compat::serde_yaml())?;
    assert_eq!(compat, serde_yaml_ng::to_string(&before)?);
    print!("{compat}");
    Ok(())
}
Output
name: web
port: 8080
tags:
  - api
  - internal
---
name: web
port: 8080
tags:
- api
- internal

Both reads give the same value. The output differs in one way by default: oneyaml indents a sequence under its key. Compat::serde_yaml() writes serde_yaml’s layout, and the assertion in the program checks it byte for byte against serde_yaml_ng’s output. Use it while snapshot tests or diffs depend on the old layout.

What changes when you move to oneyaml

The migration guide lists every difference with its reason. The ones most code meets:

  • YAML 1.2 by default. oneyaml reads the YAML 1.2 Core schema. For example, serde_yaml_ng 0.10.0 reads mask: 0b1010 as the number 10, and oneyaml reads it as a string, as the Core schema does. Compat::serde_yaml() brings back serde_yaml’s reading (10 here), and every YAML 1.1 behavior is a named setting.
  • Merge keys (<<) are an opt-in setting, merge_keys, rather than a separate Value::apply_merge step (anchors, aliases and merge keys).
  • Value keeps more. It keeps every tag the schema doesn’t resolve, including !!binary and !!set; holds integers of any size; and indexing never panics.
  • from_slice decodes UTF-8, UTF-16 and UTF-32, so it can’t lend &str fields out of the input: its target is DeserializeOwned. To borrow, use from_str.
  • Limits. Every read has resource limits; use Limits::untrusted() for network input (untrusted input).

What you get beyond a fork

  • Edits that keep the file. A Document changes only what you touch, and every comment stays with its entry, which serde_yaml’s Value can’t do (edit YAML without losing comments).
  • Errors with a fix. Each error has a Y#### code, a source snippet and a suggested fix (errors with line and column).
  • JSON Patch and merge patches on the YAML text, all or nothing (JSON Patch for YAML).
  • No unsafe and no panics on any input.

A fork is the better choice when you need serde_yaml’s exact behavior without reviewing the differences, or wall-clock read speed on files where serde_yaml_ng is faster (oneyaml’s benchmark tables name them). To weigh all the crates, see choosing a Rust YAML crate, and to start, the getting started guide.