skip_serializing_if for Sparse Output
Use #[serde(skip_serializing_if = "Option::is_none")] to omit None fields from generated JSON. The standard PATCH-request pattern.
Detailed Explanation
Omitting None from JSON Output
By default, an Option<T> field with value None serializes as "field": null. For PATCH requests and sparse responses, you usually want the field to disappear entirely. #[serde(skip_serializing_if = "Option::is_none")] does exactly that.
Example struct
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateUser {
pub id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub age: Option<i32>,
}
Behaviour
let patch = UpdateUser {
id: 1,
name: Some("Ada".into()),
email: None,
age: None,
};
let body = serde_json::to_string(&patch)?;
// {"id":1,"name":"Ada"}
email and age are not in the output. The receiving server treats their absence as "do not change" rather than "set to null".
Why this is the PATCH pattern
REST PATCH semantics distinguish three states for each field:
- Field absent → leave the existing value alone.
- Field present with value → update to the new value.
- Field present with null → clear the column.
Without skip_serializing_if, you cannot express state 1 — every None becomes state 3, which can wipe data unintentionally.
Custom skip predicates
Option::is_none is the most common case, but any function returning bool works:
fn is_zero(n: &i32) -> bool { *n == 0 }
#[serde(skip_serializing_if = "is_zero")]
pub retries: i32,
This omits retries from the output when the value is zero — handy for opting out of optional config keys when the default is acceptable.
Pair with default for full round-trip
Combine with #[serde(default)] to also accept a missing key on deserialization:
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
Now the field round-trips cleanly whether the JSON includes it or not.
Use Case
PATCH endpoints in REST APIs, sparse mutation payloads, and any case where you need to distinguish 'do not modify' from 'clear the value' should use this attribute. It is the canonical Rust + serde pattern for partial updates.