serde_ignored
Find out which JSON, YAML, or TOML keys get silently dropped during Serde deserialization
Repository Health
Technical Analysis
serde_ignored wraps any existing Serde Deserializer and invokes a callback with the dotted path of every field that gets ignored while deserializing into a struct. Rather than silently discarding unrecognized keys, you get a stream of paths like dependencies.serde.typo1 or typo2, which is exactly the kind of feedback config file parsers and CLI tools want to surface to users as a warning.
The crate works by intercepting deserialize_ignored_any, the hook Serde already calls internally whenever a map or struct field has no matching target field, so it requires no changes to your existing Deserialize derives. It ships #![no_std] (with alloc) and has a single production dependency, making it cheap to drop into config-loading code, most famously inside Cargo itself for catching typos in Cargo.toml.
What You Get
- A single
serde_ignored::deserialize(deserializer, callback)entry point that wraps any existing SerdeDeserializertransparently - A
Pathenum that renders the exact location of an ignored field as a dotted string, e.g.dependencies.serde.typo1 - Zero changes required to your existing
Deserializederives or struct definitions #![no_std]support (withalloc) for use in constrained environments- Works with any format that has a Serde
Deserializerimplementation — JSON, YAML, TOML, etc., not justserde_json
Common Use Cases
- Warning users about typo’d or deprecated keys in a config file (TOML, YAML, JSON) instead of silently ignoring them
- Auditing
Cargo.toml-style manifest files for unrecognized fields, the motivating use case inside Cargo itself - Surfacing schema drift when consuming third-party or user-supplied JSON payloads during a migration
- Building lint/validation tooling on top of existing deserialization code without duplicating the schema
Under The Hood
Architecture The crate is a single-file (src/lib.rs) implementation built around one core struct, Deserializer<'a, 'b, D, F>, which wraps an arbitrary inner D: de::Deserializer<'de> plus a &mut F: FnMut(Path) callback and a borrowed Path<'a> representing the current traversal location. Rather than re-implementing deserialization, it implements Serde’s full Deserializer trait and forwards every method (deserialize_bool, deserialize_u8, deserialize_map, deserialize_seq, …) straight through to the inner deserializer, wrapping the visitor in a Wrap type that carries the callback and current path down through nested SeqAccess/MapAccess calls. The one method that actually does work is deserialize_ignored_any, which Serde’s derive macro already calls internally whenever it encounters a map key with no matching struct field — that single interception point is where the callback fires with the current Path. Path is deliberately a borrowed, stack-allocated linked list (Root, Seq { parent, index }, Map { parent, key }, Some { parent }, NewtypeStruct, NewtypeVariant) rather than an owned Vec<String>, so traversing arbitrarily deep structures costs no heap allocation until Display::fmt walks the parent chain and formats the final dotted path string on demand. Tech Stack The crate declares #![no_std] and depends only on alloc plus serde_core (re-exported internally as serde) pinned to >=1.0.220, which is the trait-only split of Serde introduced for faster compile times and no-std compatibility; a [target.'cfg(any())'.dependencies] block additionally pulls in the full serde crate purely to keep its version in the lockfile without actually compiling it in. Dev-dependencies (serde, serde_derive, serde_json) are used only for tests and the doc example. Edition 2021, MSRV pinned at 1.71 and enforced in CI. Code Quality tests/test.rs centers on a single assert_ignored helper that deserializes JSON via serde_json::Deserializer, collects reported paths into a BTreeSet, and asserts them against an expected set; the four tests cover the README’s nested-struct example plus sequences, options, and enum variants, exercising most of the Path enum’s branches. There is no unsafe code anywhere in the crate. CI (dtolnay/serde-ignored/.github/workflows/ci.yml) runs the test suite across nightly/beta/stable/MSRV Rust with -Dwarnings, adds a minimal-versions dependency check, and lints the rustdoc build separately — a notably thorough matrix for a ~40KB crate. API Design The public surface is intentionally minimal: one function, serde_ignored::deserialize(deserializer, callback), that a caller drops around an existing Deserializer with zero changes to their Deserialize derives — the doc comment on lib.rs doubles as a runnable doctest matching the README example verbatim, so there’s effectively no gap between reading the docs and using the crate correctly.