Search code examples
rustserde

Rust Serde: field with deserialize_with which depends on 2 other fields throws error `missing field`


I can demonstrate my problem with this simple code:

#[derive(Debug, Deserialize)]
struct Person {
    first_name: String,
    last_name: String,
    #[serde(deserialize_with = "full_name_deserializer")]
    full_name: String,
}

fn full_name_deserializer<'de, D>(deserializer: D) -> Result<String, D::Error>
where
    D: Deserializer<'de>,
{
    let person: Person = Deserialize::deserialize(deserializer)?;    
    Ok(format!("{} - {}", person.first_name, person.last_name))
}

let data = r#"
    {
        "first_name": "John",
        "last_name": "Doe"
    }"#;

match serde_json::from_str::<Person>(data) {
    Ok(p) => println!("Me: {:?}",p),
    Err(e) => println!("Error: {:?}",e)
}

This throws error: Error("missing field full_name", line: 5, column: 9)

The full_name field will obviously be missing in the json because I want it to be initialized as the concatenation of 2 other fields. How can I do that?

EDIT: one idea I have is to remove deserialize_with and to add skip_deserializing to the full_name field and then post process the struct to add the full_name after the deserialization is complete. I am not sure how to hook into the time when deserialization is complete? Is there a built in way? Or will I have to do it manually?

I did come across this:

https://github.com/serde-rs/serde/issues/642

which talks about "Add finalizer attribute hook to validate a deserialized structure". That might get me what I need. But I don't think it's been added to serde yet.


Solution

  • This does not work because it is missing the field. deserialize_with gives you a deserializer for the field you want to deserialize, not the parent structure. The way it is currently written, it expects to find data which looks like this:

    {
        "first_name": "John",
        "last_name": "Doe",
        "full_name": {
            "first_name": "John",
            "last_name": "Doe"
        }
    }
    

    That being said, it would still not work because the implementation of Person::deserialize would call itself in an infinite loop until it eventually finds the inner field missing or overflows the stack.

    You can fix this by implementing Deserialize for the entire type, instead of just the derived field. Alternatively, you may find it easier to use serde(from = "...") like so:

    #[derive(Debug, Deserialize)]
    #[serde(from = "PersonData")]
    struct Person {
        first_name: String,
        last_name: String,
        full_name: String,
    }
    
    #[derive(Debug, Deserialize)]
    struct PersonData {
        first_name: String,
        last_name: String,
    }
    
    impl From<PersonData> for Person {
        fn from(data: PersonData) -> Self {
            Person {
                full_name: format!("{} - {}", data.first_name, data.last_name),
                first_name: data.first_name,
                last_name: data.last_name,
            }
        }
    }
    

    Rust Playground