How to handle a missing field in JSON
Short answer: When a key can be absent, make the field an Option<T> — serde fills it with None whether the key is missing or null — or give the field a fallback with #[serde(default)]. Pick Option when the code has to notice the field is gone; pick default when there is a value to fall back to and nothing downstream should have to branch.
This continues the json-demo project from the earlier pages in this chapter — cargo new json-demo, with the same two crates:
[dependencies]serde = { version = "1.0.229", features = ["derive"] }serde_json = "1.0.154"A required field that the API sometimes omits fails with missing field when the payload arrives — that error is on the deserialisation page. A key that is spelled differently is a rename problem instead, and rename and alias are on the previous page. This page is about the two remaining cases: the key is not there at all, or it is there and null.
Option: the field may not be there
Section titled “Option: the field may not be there”use serde::Deserialize;
#[derive(Debug, Deserialize)]struct User { id: u64, full_name: Option<String>,}
fn main() { let with = r#"{"id": 7, "full_name": "Grace Hopper"}"#; let without = r#"{"id": 7}"#; let null = r#"{"id": 7, "full_name": null}"#; for text in [with, without, null] { let User { id, full_name } = serde_json::from_str(text).unwrap(); println!("id {id}, full_name {full_name:?}"); }}id 7, full_name Some("Grace Hopper")id 7, full_name Noneid 7, full_name NoneNo attribute is needed — serde treats Option as optional by itself. An absent key and an explicit null both arrive as None, and the two are indistinguishable afterwards. That is the trade: the type forces every reader of the field to decide what None means, with a match or an unwrap_or, instead of quietly inventing a value.
#[serde(default)]: a fallback the code never sees
Section titled “#[serde(default)]: a fallback the code never sees”use serde::Deserialize;
#[derive(Debug, Deserialize)]struct User { id: u64, #[serde(default)] full_name: String,}
fn main() { let without = r#"{"id": 7}"#; let with = r#"{"id": 7, "full_name": "Grace Hopper"}"#; for text in [without, with] { let User { id, full_name } = serde_json::from_str(text).unwrap(); println!("id {id}, full_name {full_name:?}"); }}id 7, full_name ""id 7, full_name "Grace Hopper"#[serde(default)] fills an absent field with Default::default() for its type: "" for a String, 0 for numbers, false for a bool. The field stays a plain String, so the rest of the program never learns the key was missing.
default does not accept null
Section titled “default does not accept null”use serde::Deserialize;
#[derive(Debug, Deserialize)]struct User { id: u64, #[serde(default)] full_name: String,}
fn main() { let text = r#"{"id": 7, "full_name": null}"#; match serde_json::from_str::<User>(text) { Ok(user) => println!("id {}, full_name {:?}", user.id, user.full_name), Err(e) => println!("{e}"), }}invalid type: null, expected a string at line 1 column 27The default fires only when the key is absent. A null is a present value of the wrong type, and the error says so. So: the API omits the key and default is enough; the API sends null and the field has to be an Option.
A fallback of your own
Section titled “A fallback of your own”use serde::Deserialize;
fn default_port() -> u16 { 8080}
#[derive(Debug, Deserialize)]struct Config { host: String, #[serde(default = "default_port")] port: u16,}
fn main() { let minimal = r#"{"host": "localhost"}"#; let full = r#"{"host": "localhost", "port": 9000}"#; for text in [minimal, full] { let Config { host, port } = serde_json::from_str(text).unwrap(); println!("{host}:{port}"); }}localhost:8080localhost:9000#[serde(default = "default_port")] takes the path to a function — no parentheses — and uses its return value when the key is missing. The attribute also goes on the struct itself, where it covers every field at once: serde starts from Self::default() and overwrites the keys the JSON contains, which is why that form needs #[derive(Default)] on the struct.
Which one to pick
Section titled “Which one to pick”Optionwhen “missing” is information — the field’s absence changes what the program does.defaultwhen every caller wants the same fallback and the field should read like any other.#[serde(default)]on anOptionfield changes nothing: an absent key already producesNone.- Going the other way, a
Noneserialises asnullunless the field carriesskip_serializing_if— that is on the serialisation page.
- How to rename JSON fields with serde — the previous page:
rename,rename_all, andalias - How to deserialize JSON into a struct — where the
json-demoproject starts - Back to Working with JSON, TOML and CSV