在不同 crate 中为类型派生 De/Serialize

Rust 的孤儿规则要求实现 trait 的 crate 或实现 trait 的类型必须和 impl 在同一个 crate 中定义,因此不可能直接为不同 crate 中的类型实现 SerializeDeserialize

- use serde::Serialize;
- use other_crate::Duration;
-
- // 孤儿规则不允许
- impl Serialize for Duration {
-     /* ... */
- }

为了解决这个问题,Serde 提供了一种方法,可以为其他 crate 中类型派生 SerializeDeserialize 实现。唯一需要注意的是,你必须提供类型的定义让 Serde 的派生可以处理。在编译时,serde 将检查你提供的定义中的所有字段是否和远程类型中的字段匹配。

// 假装这是别人的 crate,而不是一个模块
mod other_crate {
    // Serde 和其他 crate 都没有为这个结构体提供 Serialize 和 Deserialize 的实现
    pub struct Duration {
        pub secs: i64,
        pub nanos: i32,
    }
}

////////////////////////////////////////////////////////////////////////////////

use other_crate::Duration;
use serde::{Serialize, Deserialize};

// Serde 将其称为远程类型的定义。这只是远程数据结构的一个副本。`remote`属性指定了我们要派生代码的实际类型路径。
#[derive(Serialize, Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef {
    secs: i64,
    nanos: i32,
}

// 现在远程类型可以像本来就有自己的 Serialize 和 Deserialize 实现一样使用。`with`属性指定了远程类型的定义路径。请注意字段的实际类型是远程类型,而不是定义类型。
#[derive(Serialize, Deserialize)]
struct Process {
    command_line: String,

    #[serde(with = "DurationDef")]
    wall_time: Duration,
}

fn main() {}

如果远程类型是一个具有所有公共字段或枚举的结构体,那就是全部内容了。如果远程类型是一个具有一个或多个私有字段的结构体,那么必须为私有字段提供 getter,并提供一个转换来构造远程类型。

// 假装这是别人的 crate,而不是一个模块
mod other_crate {
    // Serde 和其他 crate 都没有为这个结构体提供 Serialize 和 Deserialize 的实现。哦,而且字段是私有的
    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};

// 为远程结构体的每个私有字段提供 getter。getter 必须返回 `T` 或 `&T`,其中 `T` 是字段的类型。
#[derive(Serialize, Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef {
    #[serde(getter = "Duration::seconds")]
    secs: i64,
    #[serde(getter = "Duration::subsec_nanos")]
    nanos: i32,
}

// 提供一个转换来构造远程类型。
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,
}

fn main() {}

直接调用远程实现

如上所示,远程实现旨在通过某些其他结构体字段上的 #[serde(with = "...")]属性来调用。

直接调用远程实现,例如如果这是正在序列化或反序列化的顶层类型,则由于孤儿规则的存在,可能会稍微复杂。这些远程派生最终生成的代码不是 SerializeDeserialize 实现,而是具有相同签名的关联函数。

#![allow(dead_code)]

use serde::Deserialize;

struct Duration {
    secs: i64,
    nanos: i32,
}

// 严格来说,这个派生并未为 Duration 生成 Deserialize 实现,也没有为 DurationDef 生成 Deserialize 实现。
//
// 相反,它生成了一个叫做 DurationDef::deserialize 的反序列化方法,其返回类型是 Duration。这个方法具有与为 Duration 派生 Deserialize 实现相同的签名,但并不是一个 Deserialize 实现。
#[derive(Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef {
    secs: i64,
    nanos: i32,
}

fn main() {}

知道这些,生成的方法可以通过传递一个 Deserializer 实现来直接调用。

#![allow(dead_code)]

use serde::Deserialize;

struct Duration;

#[derive(Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef;

fn try_main(j: &str) -> Result<Duration, serde_json::Error> {
let mut de = serde_json::Deserializer::from_str(j);
let dur = DurationDef::deserialize(&mut de)?;

// `dur` 的类型是 Duration
    Ok(dur)
}

fn main() {}

另外我们可以编写一个顶层的 newtype 包装器作为私有助手来反序列化远程类型。

#![allow(dead_code)]

use serde::Deserialize;

struct Duration;

#[derive(Deserialize)]
#[serde(remote = "Duration")]
struct DurationDef;

fn try_main(j: &str) -> Result<Duration, serde_json::Error> {
#[derive(Deserialize)]
struct Helper(#[serde(with = "DurationDef")] Duration);

let dur = serde_json::from_str(j).map(|Helper(dur)| dur)?;

// `dur` 的类型是 Duration
    Ok(dur)
}

fn main() {}