Search code examples
rustserializationserde

Replicating the behavior of serde(with) in custom serializer


I have a custom chrono::DateTime<Utc> serializer written:

pub mod datetime {
    pub fn serialize<S: Serializer>(
        date: &DateTime<Utc>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        // ...
    }

    // deserialize omitted
}

I need to implement custom serialization logic for a struct and I'm having difficulties emulating the behavior of serde(with = "datetime") as attribute on a struct field. What I'm trying to replicate:

#[derive(Serialize, Deserialize)]
struct Foo {
    #[serde(with = "datetime")]
    pub timestamp: DateTime<Utc>,
}

My attempt:

impl Serialize for Foo {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut m = serializer.serialize_map(None)?;
        m.serialize_entry(
            "timestamp",
            &datetime::serialize(&self.timestamp, serializer)?,
        )?;
        m.end()
    }
}

Results in an error:

error[E0277]: the trait bound `<S as foo::_::_serde::Serializer>::Ok: Serialize` is not satisfied
    --> <...>/foo.rs:95:13
     |
93   |         m.serialize_entry(
     |           --------------- required by a bound introduced by this call
94   |             "timestamp",
95   |             &datetime::serialize(&self.sent_timestamp, serializer)?,
     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `<S as foo::_::_serde::Serializer>::Ok`
     |
note: required by a bound in `serialize_entry`
    --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/serde-1.0.193/src/ser/mod.rs:1808:12
     |
1801 |     fn serialize_entry<K: ?Sized, V: ?Sized>(
     |        --------------- required by a bound in this associated function
...
1808 |         V: Serialize,
     |            ^^^^^^^^^ required by this bound in `SerializeMap::serialize_entry`
help: consider further restricting the associated type
     |
86   |     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> where <S as foo::_::_serde::Serializer>::Ok: Serialize {
     |

Have I misunderstood the usage of Serializer? I am not entirely sure what I'm doing wrong here.


Solution

  • As suggested in the comments, looking at the expansion of #[derive(Serialize)] with #[serde(with)] will help us see what exactly the derived code is doing, allowing us to replicate its behavior. This can be done by copying your code into the Rust playground and using the "Expand macros" option in the Tools drop-down menu.

    When looking at the expanded code, we see something that looks like the following (though I have cleaned it up significantly to make it more readable):

    struct SerializeWith<'a>(&'a DateTime<Utc>);
    
    impl Serialize for SerializeWith<'_> {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            datetime::serialize(&self.0, serializer)
        }
    }
    

    Therefore, the proper way to do this is to define a newtype that calls directly to the custom serialization logic. The full correct Serialize implementation is then:

    impl Serialize for Foo {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            struct SerializeWith<'a>(&'a DateTime<Utc>);
    
            impl Serialize for SerializeWith<'_> {
                fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                    datetime::serialize(&self.0, serializer)
                }
            }
    
            let mut m = serializer.serialize_struct("Foo", 1)?;
    
            m.serialize_field("timestamp", &SerializeWith(&self.timestamp))?;
    
            m.end()
        }
    }