Option<T> for Nullable JSON Fields
Wrap fields that can be null in Option<T>. Learn how serde handles missing keys, explicit null, and the difference between the two.
Detailed Explanation
Modeling Nullable Fields with Option
Rust does not have a native null. The idiomatic way to represent "this value may or may not exist" is Option<T>, where Some(value) is a present value and None is missing.
Example JSON
{
"id": 1,
"nickname": "ada",
"deleted_at": null
}
Generated Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub id: i32,
pub nickname: String,
pub deleted_at: Option<serde_json::Value>,
}
Refining the inner type
The converter cannot guess the type behind null. Replace Option<serde_json::Value> with the actual type once you know it:
pub deleted_at: Option<chrono::DateTime<Utc>>,
Missing keys vs explicit null
By default, serde treats a missing key as a deserialization error. To accept either a missing key or null, add the default attribute:
#[serde(default)]
pub deleted_at: Option<DateTime<Utc>>,
Now the field becomes None whether the key is absent or present-and-null.
Skipping null on serialization
To avoid emitting "deleted_at": null when the value is None, combine Option<T> with #[serde(skip_serializing_if = "Option::is_none")]:
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTime<Utc>>,
This is the standard pattern for PATCH request bodies, sparse REST responses, and database update payloads where you do not want to overwrite columns with NULL.
Use Case
Soft-deleted database rows, optional metadata, and partial PATCH update payloads all rely on Option<T>. Getting the pattern right is the difference between safe round-trips and accidental data loss.