Serde Code Review
by @anderskev
Reviews serde serialization code for derive patterns, enum representations, custom implementations, and common serialization bugs. Use when reviewing Rust co...
clawhub install serde-code-reviewπ About This Skill
name: serde-code-review description: Reviews serde serialization code for derive patterns, enum representations, custom implementations, and common serialization bugs. Use when reviewing Rust code that uses serde, serde_json, toml, or any serde-based serialization format. Covers attribute macros, field renaming, and format-specific pitfalls.
Serde Code Review
Review Workflow
1. Check Cargo.toml β Note serde features (derive, rc), format crates (serde_json, toml, bincode, etc.), and Rust edition (2024 has breaking changes affecting serde code)
2. Check derive usage β Verify Serialize and Deserialize are derived appropriately
3. Check enum representations β Enum tagging affects wire format compatibility and readability
4. Check field attributes β Renaming, defaults, skipping affect API contracts
5. Check edition 2024 compatibility β Reserved gen keyword, RPIT lifetime capture changes, never_type_fallback
6. Verify round-trip correctness β Serialized data must deserialize back to the same value
Gates (before reporting findings)
Run in order. Do not write a finding until the step that applies has passed.
1. Serde context on disk β Pass when: You have read the relevant Cargo.toml (crate or workspace root) and can state Rust edition, serde / serde_derive features if non-default (derive, rc), and which format crates apply (serde_json, toml, bincode, etc.) for the code under review. Then apply edition-specific checklist items (e.g. gen, RPIT/never_type_fallback) only when that file supports them.
2. Per-finding evidence β Pass when: Each issue cites [FILE:LINE] from the current tree for the struct/enum, Serialize/Deserialize impl, or attribute block in question (not from memory, docs-only, or another branch).
3. Category check vs protocol β Pass when: For the finding type (derive attrs, enum tagging, flatten, custom impl, sqlx + serde alignment), you ran the matching checks from beagle-rust:review-verification-protocol (e.g. full type definition + serde attrs before βwrong representationβ; confirmed edition in Cargo.toml before edition-2024-only findings). Then add the finding.
4. Output shape β Pass when: The report lines match Output Format below (severity + description).
Output Format
Report findings as:
[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.
Quick Reference
| Issue Type | Reference | |------------|-----------| | Derive patterns, attribute macros, field configuration | references/derive-patterns.md | | Custom Serialize/Deserialize, format-specific issues | references/custom-serialization.md |
Review Checklist
Derive Usage
#[derive(Serialize, Deserialize)] on types that cross serialization boundaries#[derive(Debug)] alongside serde derives (debugging serialization issues)#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]#[expect(unused)] over #[allow(unused)] for serde-only fields (self-cleaning lint suppression, stable since 1.81)Enum Representation
#[serde(rename_all = "...")] used consistently across the APIField Configuration
#[serde(skip_serializing_if = "Option::is_none")] for optional fields (clean JSON output)#[serde(default)] for fields that should have fallback values during deserialization#[serde(rename = "...")] when Rust field names differ from wire format#[serde(flatten)] used judiciously (can cause key collisions)#[serde(deny_unknown_fields)] on types that need forward compatibilitygen β reserved keyword in edition 2024 (use r#gen or rename)Database Integration (sqlx)
#[derive(sqlx::Type)] enums use consistent representation with serderename_all) and sqlx (rename_all)Edition 2024 Compatibility
gen (reserved keyword β use r#gen with #[serde(rename = "gen")] or choose a different name)Serialize/Deserialize impls returning impl Trait account for RPIT lifetime capture changes (all in-scope lifetimes captured by default; use + use<'a> for precise control)never_type_fallback β ! falls back to ! instead of (), which affects match exhaustiveness on Result patternsCorrectness
PartialEq derived for types with round-trip testsf64 β i64 in JSON numbers)Decimal used for money/precision-sensitive values, not f64Severity Calibration
Critical
#[serde(rename)] causing API-breaking field name changes#[serde(flatten)] causing silent key collisionsf64 precision loss for monetary values)Major
rename_all across related types (confusing API)skip_serializing_if causing null/empty noise in outputdeny_unknown_fields on types consumed by evolving APIs (breaks forward compatibility)gen without r#gen escape (edition 2024 compile failure)Minor
#[serde(default)] on required fields#[allow(unused)] instead of #[expect(unused)] for serde-only fields (prefer self-cleaning lint suppression)Informational
#[non_exhaustive] alongside serde for forward compatibilityValid Patterns (Do NOT Flag)
#[serde(untagged)] enums β Valid when discriminated by structure, not by tagserde_json::Value for dynamic data β Appropriate for truly schema-less fields#[serde(skip)] on computed fields β Correct for derived/cached values#[serde(with = "...")] for custom formats β Standard for dates, UUIDs, etc.r#gen with #[serde(rename = "gen")] β Correct edition 2024 workaround for gen fields in wire formats+ use<'a> on custom serializer return types β Precise RPIT lifetime capture (edition 2024)Before Submitting Findings
Complete Gates (before reporting findings) above; gate 3 incorporates beagle-rust:review-verification-protocol for serde-related issue types.