Deriving De/Serialize for type in a different crate
Rust's orphan rule requires that either the trait or the type for which you
are implementing the trait must be defined in the same crate as the impl, so it
is not possible to implement Serialize
and Deserialize
for a type in a
different crate directly.
- use serde::Serialize;
- use other_crate::Duration;
-
- // Not allowed by orphan rule.
- impl Serialize for Duration {
- /* ... */
- }
To work around this, Serde provides a way of deriving Serialize
and
Deserialize
implementations for types in other people's crates. The only catch
is that you have to provide a definition of the type for Serde's derive to
process. At compile time, Serde will check that all the fields in the definition
you provided match the fields in the remote type.
// Pretend that this is somebody else's crate, not a module.
mod other_crate {
// Neither Serde nor the other crate provides Serialize and Deserialize
// impls for this struct.
pub struct Duration {
pub secs: i64,
pub nanos: i32,
}
}
////////////////////////////////////////////////////////////////////////////////
use other_crate::Duration;
use serde::{Serialize, Deserialize};
// Serde calls this the definition of the remote type. It is just a copy of the
// remote data structure. The `remote` attribute gives the path to the actual
// type we intend to derive code for.
#[derive(Serialize, Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef {
secs: i64,
nanos: i32,
}
// Now the remote type can be used almost like it had its own Serialize and
// Deserialize impls all along. The `with` attribute gives the path to the
// definition for the remote type. Note that the real type of the field is the
// remote type, not the definition type.
#[derive(Serialize, Deserialize)]
struct Process {
command_line: String,
#[serde(with = "DurationDef")]
wall_time: Duration,
}
If the remote type is a struct with all public fields or an enum, that's all there is to it. If the remote type is a struct with one or more private fields, getters must be provided for the private fields and a conversion must be provided to construct the remote type.
// Pretend that this is somebody else's crate, not a module.
mod other_crate {
// Neither Serde nor the other crate provides Serialize and Deserialize
// impls for this struct. Oh, and the fields are private.
pub struct Duration {
secs: i64,
nanos: i32,
}
impl Duration {
pub fn new(secs: i64, nanos: i32) -> Self {
Duration { secs: secs, nanos: nanos }
}
pub fn seconds(&self) -> i64 {
self.secs
}
pub fn subsec_nanos(&self) -> i32 {
self.nanos
}
}
}
////////////////////////////////////////////////////////////////////////////////
use other_crate::Duration;
use serde::{Serialize, Deserialize};
// Provide getters for every private field of the remote struct. The getter must
// return either `T` or `&T` where `T` is the type of the field.
#[derive(Serialize, Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef {
#[serde(getter = "Duration::seconds")]
secs: i64,
#[serde(getter = "Duration::subsec_nanos")]
nanos: i32,
}
// Provide a conversion to construct the remote type.
impl From<DurationDef> for Duration {
fn from(def: DurationDef) -> Duration {
Duration::new(def.secs, def.nanos)
}
}
#[derive(Serialize, Deserialize)]
struct Process {
command_line: String,
#[serde(with = "DurationDef")]
wall_time: Duration,
}
Invoking the remote impl directly
As shown above, the remote impl is intended to be invoked through a
#[serde(with = "...")]
attribute on a field of some other struct.
Invoking the remote impl directly, such as if this is the top-level type being
serialized or deserialized, is somewhat more complicated because of the orphan
rules as mentioned. The code ultimately generated by these remote derives are
not Serialize
and Deserialize
impls but associated functions with the same
signature.
// Technically this derive does not produce a Deserialize impl for Duration, nor
// a Deserialize impl for DurationDef.
//
// Instead it produces a deserialization method DurationDef::deserialize whose
// return type is Duration. The method has the same signature as a Deserialize
// impl for Duration would have but is not a Deserialize impl.
#[derive(Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef {
secs: i64,
nanos: i32,
}
Knowing this, the generated method can be invoked directly by passing a
Deserializer
implementation.
let mut de = serde_json::Deserializer::from_str(j);
let dur = DurationDef::deserialize(&mut de)?;
// `dur` has type Duration
Alternatively we can write a top-level newtype wrapper as a private helper for deserializing the remote type.
#[derive(Deserialize)]
struct Helper(#[serde(with = "DurationDef")] Duration);
let dur = serde_json::from_str(j).map(|Helper(dur)| dur)?;
// `dur` has type Duration