Data & APIs

Missing vs Null in Serde: What Option<T> Preserves

Understand how optional Rust fields handle absent keys, explicit null, default attributes and serialized output.

Photorealistic editorial photo for Serde Optional Null Missing
Data & APIs · Practical ideas for your next build

An absent property and a property whose value is null are different JSON states. A typical Rust Option field intentionally combines them into None during deserialization. That is convenient for many response models, but it can lose information needed by patch requests and update APIs.

Decide whether two states are enough

For a profile response, a missing nickname and a null nickname may both mean there is no display nickname. For an update request, missing can mean leave the value unchanged while null means clear it. A model that stores only Option<String> cannot remember which of those two inputs produced None. Define the update semantics before reusing a response struct as a request model.

Keep defaulting and output omission separate

The serde default attribute describes what to do when input is missing. A skip_serializing_if attribute describes whether to emit a field when writing output. These are independent choices. Our generator emits defaults and skips None fields in output, so round-tripping a null field can produce an absent field. That behavior is documented rather than presented as a lossless preservation of JSON syntax.

Test all relevant presence cases

Keep fixtures for a missing key, an explicit null, and a real value. If the distinction matters, use a deliberate presence wrapper or custom deserialization strategy and verify it against each fixture. Simply adding another nested Option does not guarantee the intended three-state contract without the right deserializer. Review how validation errors and serialized requests appear to the receiving service as well.

Worked example

EXAMPLE
{}
{"nickname":null}
{"nickname":"Ada"}

// A conventional Option<String> model maps the first two to None.

Does skip_serializing_if change what a parser accepts?

It controls serialization output. Input acceptance and missing-field behavior are separate deserialization decisions.

Reference and further reading

Read the official documentation ↗