---
title: "serde_yaml is deprecated: what now"
url: https://recursiveai.org/oneyaml/articles/serde-yaml-deprecated/
description: "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."
date: 2026-10-26
author: oneyaml team
---

# 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.

October 26, 2026 · oneyaml team

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

| Crate | Latest on crates.io (26 Oct 2026) | Status, per its own page |
|---|---|---|
| [serde_yaml](https://crates.io/crates/serde_yaml) | 0.9.34+deprecated, 25 Mar 2024 | Repository archived; README: “(This project is no longer maintained.)” ([GitHub](https://github.com/dtolnay/serde-yaml)) |
| [yaml_serde](https://crates.io/crates/yaml_serde) | 0.10.7, 18 Aug 2026 | “the actively maintained fork of serde-yaml … by the official YAML organization” ([README](https://github.com/yaml/yaml-serde)) |
| [serde_yaml_ng](https://crates.io/crates/serde_yaml_ng) | 0.10.0, 26 May 2024 | A 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](https://github.com/acatton/serde-yaml-ng)) |
| [serde_norway](https://crates.io/crates/serde_norway) | 0.9.42, 21 Dec 2024 | “A hard-fork of Serde YAML” ([README](https://github.com/cafkafk/serde-yaml)) |
| [serde_yml](https://crates.io/crates/serde_yml) | 0.0.13, 27 May 2026 | Its crates.io description: “DEPRECATED — `serde_yml` is unmaintained” |
| [oneyaml](https://crates.io/crates/oneyaml) | 1.2.0, 21 Oct 2026 | A 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](https://rustsec.org/advisories/RUSTSEC-2024-0320.html) is for `yaml-rust`, a different crate, not for serde_yaml. RustSec’s [serde_yaml page](https://rustsec.org/packages/serde_yaml.html) 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.toml · yaml_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:

*Terminal · Cargo*

```
cargo remove serde_yaml
cargo add oneyaml@1.2.0
```

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.rs · oneyaml 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](https://recursiveai.org/oneyaml/rust/docs/migrating-from-serde-yaml/) 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`](https://docs.rs/serde_yaml_ng/0.10.0/serde_yaml_ng/enum.Value.html#method.apply_merge) step ([anchors, aliases and merge keys](https://recursiveai.org/oneyaml/rust/cookbook/anchors-aliases-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](https://recursiveai.org/oneyaml/rust/cookbook/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](https://recursiveai.org/oneyaml/articles/edit-yaml-keep-comments/)).
- **Errors with a fix.** Each error has a `Y####` code, a source snippet and a suggested fix ([errors with line and column](https://recursiveai.org/oneyaml/rust/cookbook/errors-with-line-and-column/)).
- **JSON Patch and merge patches** on the YAML text, all or nothing ([JSON Patch for YAML](https://recursiveai.org/oneyaml/articles/json-patch-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](https://recursiveai.org/oneyaml/rust/docs/performance/) name them). To weigh all the crates, see [choosing a Rust YAML crate](https://recursiveai.org/oneyaml/articles/rust-yaml-parser/), and to start, the [getting started guide](https://recursiveai.org/oneyaml/rust/docs/getting-started/).
