Search code examples
rustserdeserde-json

Serialize a remote struct with private String


I need to serialize a struct from a remote crate and all of the fields in the struct are private. There are getter's implemented in the remote struct to get those values. I am following this guidance and got it to work just fine for primitive types. However, I'm struggling with how to implement this for non-primitive types (ie: String) that the remote struct contains.

Below is a small piece of what I've implemented to frame the issue. The DataWrapper struct simply wraps Data, where Data is the remote struct.

#[derive(Serialize)]
pub struct DataWrapper {
    #[serde(with = "DataDef")]
    pub data: Data,
}

#[derive(Serialize)]
#[serde(remote = "remote_crate::data::Data")]
pub struct DataDef {
    #[serde(getter = "Data::image_id")]     // This works
    image_id: u16,

    #[serde(getter = "Data::description")]  // This does not work
    description: String,
}

The error I get when compiling this is

#[derive(Serialize)]
         ^^^^^^^^^ expected struct `std::string::String`, found `&str`

This makes sense, since the getter Data::description returns &str rather than a String. But, I'm not seeing a way in my code to coerce this so the compiler is happy.

If I change DataDef::description to be &str instead of String, then I have to implement lifetimes. But, when I do that, the compiler then says the remote "struct takes 0 lifetime arguments".

Appreciate any tips on how I can serialize this and other non-primitive types.


Solution

  • One approach you could do, so that you have full control of the serialization. Is to have the data wrapper be a copy of the struct fields you need, instead of the entire remote struct. Then you can implement From<remote_crate::data::Data> for DataWrapper and use serde without trying to coerce types.

    #[derive(Serialize)]
    pub struct Data {
        image_id: u16,
        description: String,
    }
    
    impl From<remote_crate::data::Data> for Data {
        fn from(val: remote_crate::data::Data) -> Self {
            Self {
                image_id: val.image_id,
                description: val.description.to_string(),
            }
        }
    }
    
    
    // Then you could use it like this:
    // let my_data: Data = data.into();