rust-serde
Skill0 starsChanged 8 months agoMITView on GitHub
Serde derive macros, custom serializers/deserializers, enum representations (tagged, untagged, adjacently tagged), serde attributes (rename, default, flatten, skip, with), zero-copy deserialization, format selection (JSON, TOML, bincode), DTO patterns, and feature-gating serde in libraries. Use when working with serialization, deserialization, JSON APIs, config files, wire formats, or data conversion between Rust types and external representations.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- po4yka/rust-skillstheir own version, first seen Nov 2, 2026
- uswuth/rustoriatheir own version, first seen Oct 25, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- po4yka/rust-skills★ 5same name
- uswuth/rustoria★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository16 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- rust-apiSkill
- rust-architectureSkill
- rust-asyncSkill
- rust-ci-deploySkill
- rust-databaseSkill
- rust-dddSkill
- rust-errorsSkill
- rust-macrosSkill
- rust-ownershipSkill
- rust-perfSkill
- rust-qualitySkill
- rust-testsSkill
- rust-tracingSkill
- rust-typesSkill
- rust-unsafeSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 22
Every Serialize / Deserialize derive is a contract with the outside world.
- Distinctive: line 90
Use adjacently tagged: when you need versioned payloads or the content can be any type.
- Distinctive: line 93
Use untagged: only as a last resort — error messages are poor and it tries variants in order.
- Distinctive: line 126
Use an optional feature:
- Very specific: line 173
Never put serde derives on domain types directly.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Serde & Serialization
Core Question
How does this data cross the boundary between Rust types and external formats?
Every Serialize / Deserialize derive is a contract
with the outside world. The shape of your Rust types
becomes a wire format, a config schema, or a storage
layout. Get the representation wrong and you break
every consumer.
Error -> Design Question
| Symptom | Don't Just Say | Ask Instead |
| "expected struct, found string" | "Fix the JSON" | Is your enum representation correct for this format? |
"missing field x" | "Add the field" | Should this field have a default or be Optional? |
"duplicate field x" | "Remove one" | Is #[serde(flatten)] creating key collisions? |
"unknown field x" | "Ignore it" | Should you deny_unknown_fields to catch drift? |
| Custom deserializer panics | "Fix the input" | Is parse-don't-validate the right approach here? |
| "data did not match any variant" | "Add a variant" | Do you need #[serde(other)] for forward compat? |
Quick Decisions
| Situation | Reach For | Why |
| New struct for JSON API | #[derive(Serialize, Deserialize)] | Derive covers 90% of cases |
| Enum over the wire | #[serde(tag = "type")] | Internally tagged is cleanest for JSON APIs |
| Rust snake_case to JSON camelCase | #[serde(rename_all = "camelCase")] | One attribute on the container |
| Optional fields in input | Option<T> + #[serde(default)] | Missing keys become None, not errors |
Newtype wrapper (e.g., UserId(Uuid)) | #[serde(transparent)] | Serializes as the inner type |
| Reject unexpected keys | #[serde(deny_unknown_fields)] | Catches typos and schema drift early |
| Feature-gate serde in a library | serde feature + optional dep | Consumers opt in, no forced dependency |
| Flatten nested config | #[serde(flatten)] | Merges child fields into parent — watch for key collisions and perf cost |
| Zero-copy deserialization | &'de str or Cow<'de, str> | Borrows from input buffer, avoids allocation |
| Date/time custom format | #[serde(with = "module")] | Isolates format logic in a helper module |
| Sensitive fields (passwords, keys) | #[serde(skip)] | Never serialize secrets to the wire |
| Default values for missing fields | #[serde(default = "path::to_fn")] | Provides fallback without Option wrapping |
| Validate on deserialize | Custom Deserialize impl or wrapper | Enforces invariants at the boundary |
| JSON API server | serde_json | Standard, well-tested, good errors |
| Config files | toml or serde_yaml | Human-friendly formats for configuration |
| High-throughput parsing | simd-json with serde compat | SIMD-accelerated, drop-in replacement |
Enum Representation Strategies
Serde supports four enum encodings. The default (externally tagged) is rarely what you want for JSON APIs.
#[derive(Serialize, Deserialize)]
enum Message {
Text { body: String },
Image { url: String, width: u32 },
}| Strategy | Attribute | JSON Output |
| Externally tagged | (default) | {"Text": {"body": "hi"}} |
| Internally tagged | #[serde(tag = "type")] | {"type": "Text", "body": "hi"} |
| Adjacently tagged | #[serde(tag = "t", content = "c")] | {"t": "Text", "c": {"body": "hi"}} |
| Untagged | #[serde(untagged)] | {"body": "hi"} |
Use internally tagged for JSON APIs — it matches how most languages model discriminated unions.
Use adjacently tagged when you need versioned payloads or the content can be any type.
Use untagged only as a last resort — error messages are poor and it tries variants in order.
See references/enums.md for decision matrix and
forward-compatibility patterns.
The Attribute Quick Reference
| Attribute | Level | Effect |
rename_all | Container | Renames all fields/variants |
deny_unknown_fields | Container | Rejects unexpected keys |
default | Container/Field | Uses Default::default for missing fields |
tag / content | Container | Controls enum representation |
transparent | Container | Delegates to single inner field |
rename | Field/Variant | Renames one field or variant |
skip | Field/Variant | Excludes from both ser and de |
flatten | Field | Merges nested struct fields inline |
with | Field | Custom ser/de via module path |
alias | Field/Variant | Accepts alternative name on input |
default = "path" | Field | Custom default value function |
other | Variant | Catch-all for unknown variants |
See references/attributes.md for full details and
code examples.
Feature-Gating Serde in Libraries
Library crates should not force serde on consumers.
Use an optional feature:
[features]
default = []
serde = ["dep:serde"]
[dependencies]
serde = { version = "1", features = ["derive"], optional = true }#[cfg_attr(feature = "serde", derive(
serde::Serialize, serde::Deserialize,
))]
pub struct Config {
pub name: String,
pub timeout_ms: u64,
}This pattern keeps the dependency tree lean for consumers who don't need serialization.
Usage Scenarios
Scenario 1: "I'm building JSON API request/response
types"
-> Derive Serialize + Deserialize, use
rename_all = "camelCase", internally tagged enums,
and deny_unknown_fields on input types. Keep response
types permissive, input types strict.
See references/attributes.md.
Scenario 2: "I need to parse a TOML config file into
typed structs"
-> Derive Deserialize, use #[serde(default)] for
optional settings, nested structs for sections. Validate
invariants after deserialization or in a custom impl.
See references/custom.md.
Scenario 3: "My domain types differ from my wire
format"
-> Create separate DTO structs with serde derives.
Convert between domain and DTO using From/Into.
Never put serde derives on domain types directly.
See references/custom.md for DTO patterns.
Scenario 4: "I need to deserialize large JSON
payloads fast"
-> Use simd-json with serde compat, &'de str for
zero-copy strings, and avoid #[serde(flatten)] which
disables struct-level optimizations.
See references/custom.md for zero-copy patterns.
Reference Files
| File | Read When |
| references/attributes.md | Container, field, and variant attributes with examples |
| references/enums.md | Enum representation strategies, decision matrix, forward compatibility |
| references/custom.md | Custom ser/de, zero-copy, visitors, serde_with, DTO mapping |
Cross-References
| When | Check |
| Newtype wrappers, PhantomData in types | rust-types -> Quick Decisions |
| Feature gating, public API naming | rust-api -> Quick Decisions |
| DTO to domain model mapping | rust-ddd -> Quick Decisions |
| Request/response type placement | rust-architecture -> Quick Decisions |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

