Search code examples
rustserde

How to use serde serialize_with inside a custom Serializer for a struct


I have the following code and it is working as expected.

use serde::{ Serialize, Serializer };

// Define your struct with optional fields.
#[derive(Serialize)]
struct MyStruct {
    field1: i32,
    field2: Option<String>,
    #[serde(serialize_with = "custom_serialize_field3")]
    field3: Option<i32>,
    field4: i32,
    field5: Option<String>,
    custom_field: Option<String>,
}

// Define a custom serialization function for field3.
fn custom_serialize_field3<S>(field3: &Option<i32>, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer
{
    // Implement custom serialization logic for field3 here
    match field3 {
        Some(value) => serializer.serialize_i32(*value),
        None => serializer.serialize_i32(0),
    }
}

fn main() {
    let data = MyStruct {
        field1: 42,
        field2: Some("Hello".to_string()),
        field3: None,
        field4: 99,
        field5: Some("World".to_string()),
        custom_field: Some("CustomValue".to_string()), // Set a custom value here
    };

    let serialized = serde_json::to_string(&data).expect("Serialization failed");

    println!("{}", serialized);

    let data2 = MyStruct {
        field1: 42,
        field2: Some("Hello".to_string()),
        field3: Some(1000),
        field4: 99,
        field5: Some("World".to_string()),
        custom_field: Some("CustomValue".to_string()), // Set a custom value here
    };

    let serialized2 = serde_json::to_string(&data2).expect("Serialization failed");

    println!("{}", serialized2);
}

Working Playground

Now I have a condition that when field3 and field5 both are set, we should not serialise and return an Error.

error: cannot find attribute `serde` in this scope
 --> src/main.rs:7:7
  |
7 |     #[serde(serialize_with = "custom_serialize_field3")]
  |       ^^^^^
  |
  = note: `serde` is in scope, but it is a crate, not an attribute

For that I am trying to use the custom Serialiser for struct but getting error. I have made the following code and removed #[derive(Serialize)] from sstruct:

impl Serialize for MyStruct {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
        if self.field3.is_some() && self.field5.is_some() {
            return Err(serde::ser::Error::custom("Both field3 and field5 can't be present"));
        }

        let mut state = serializer.serialize_struct("MyStruct", 6)?;
        state.serialize_field("field1", &self.field1)?;
        state.serialize_field("field2", &self.field2)?;
        state.serialize_field("field3", &self.field3)?;
        state.serialize_field("field4", &self.field4)?;
        state.serialize_field("field5", &self.field5)?;
        state.serialize_field("custom_field", &self.custom_field)?;
        state.end()
    }
}

Playground


Solution

  • The easiest way would be to implement the 0 fallback inside of the Serialize implementation and remove the custom_serialize_field3 alltogether:

    impl Serialize for MyStruct {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            if self.field3.is_some() && self.field5.is_some() {
                return Err(serde::ser::Error::custom(
                    "Both field3 and field5 can't be present",
                ));
            }
    
            let mut state = serializer.serialize_struct("MyStruct", 6)?;
            state.serialize_field("field1", &self.field1)?;
            state.serialize_field("field2", &self.field2)?;
            state.serialize_field("field3", &self.field3.unwrap_or(0))?;
            state.serialize_field("field4", &self.field4)?;
            state.serialize_field("field5", &self.field5)?;
            state.serialize_field("custom_field", &self.custom_field)?;
            state.end()
        }
    }
    

    But if you must use the custom_seralize_field3 function (as suggested by your question title), you can do the same as what serde does internally:

    impl Serialize for MyStruct {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            if self.field3.is_some() && self.field5.is_some() {
                return Err(serde::ser::Error::custom(
                    "Both field3 and field5 can't be present",
                ));
            }
    
            let mut state = serializer.serialize_struct("MyStruct", 6)?;
            state.serialize_field("field1", &self.field1)?;
            state.serialize_field("field2", &self.field2)?;
            state.serialize_field("field3", {
                #[derive(Serialize)]
                #[serde(transparent)]
                struct Wrapper<'a>(
                    #[serde(serialize_with = "custom_serialize_field3")] &'a Option<i32>,
                );
                &Wrapper(&self.field3)
            })?;
            state.serialize_field("field4", &self.field4)?;
            state.serialize_field("field5", &self.field5)?;
            state.serialize_field("custom_field", &self.custom_field)?;
            state.end()
        }
    }