自定义序列化和反序列化

正如我们前面看到的,serde 通过宏为所有原始数据类型和许多复杂数据类型提供内置的序列化和反序列化。然而,在某些情况下,SERDE 可能无法自动实现。对于更复杂的数据类型,可能会发生这种情况。 在这些情况下,您需要手动实现这些功能。这些案例演示了 serde 的高级用法,它还允许重命名输出中的字段。对于日常使用,使用这些高级功能几乎是不必要的。这些对于网络、处理新协议以及其他方面来说可能更为常见。

假设我们有三个字段的结构。我们将假设 Serde 未能对此实现 Serializ e和 Deserialize ,因此我们需要手动实现这些。我们使用 Cargo 初始化我们的项目:

$ cargo new --bin serde-custom

然后声明依赖项,Cargo.toml 看起来是这样子:

[package]
name = "serde-custom"
version = "0.1.0"
authors = ["Foo <foo@bar.com>"]

[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_test = "1.0"

我们的结构体是这样子的:


# #![allow(unused_variables)]
#fn main() {
// chapter4/serde-custom/src/main.rs

// We will implement custom serialization and deserialization // for this struct
#[derive(Debug, PartialEq)]
struct KubeConfig {
port: u8,
healthz_port: u8,
max_pods: u8,
}
#}

我们需要为 Serde 派生 DebugPartialEq 在内部使用。在现实世界,可能还要手动实现这些。现在,我们需要为 kubeconfig 实现 Serialize


# #![allow(unused_variables)]
#fn main() {
pub trait Serialize {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer;
}
#}

序列化结构体的基本流程是,将简单地序列化结构名称,然后序列化每个元素,然后按顺序发出序列化结束的信号。serde 具有可用于所有基本类型的内置序列化方法,因此实现不需要担心处理内置类型。让我们看看如何序列化我们的结构体:


# #![allow(unused_variables)]
#fn main() {
// chapter4/serde-custom/src/main.rs

// Implementing Serialize for our custom struct defines
// how instances of that struct should be serialized.
// In essence, serialization of an object is equal to
// sum of the serializations of it's components
impl Serialize for KubeConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
let mut state = serializer.serialize_struct("KubeConfig", 3)?;
state.serialize_field("port", &self.port)?;
state.serialize_field("healthz_port", &self.healthz_port)?;
state.serialize_field("max_pods", &self.max_pods)?; state.end()
}
}
#}

结构的序列化总是以调用 serialize_struct 开始,结构名称和字段数作为参数(对于其他类型,有类似的命名方法)。然后,我们按照它们出现的顺序序列化每个字段,同时传递将在结果 json 中使用的键名。 完成后,我们将特定的 end 作为信号。

实现反序列化有点复杂,有一些样板代码。相关特征如下:


# #![allow(unused_variables)]
#fn main() {
pub trait Deserialize<'de>: Sized {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>;

}
#}

为类型实现此操作需要实现访问者模式。 Serde 定义了一个特殊的 Visitor 特征,如下面的示例所示。请注意,这有所有内置类型的 visit_ * 方法,这里没有显示。 此外,在下面的示例中,我们使用符号 ... 来表示此处有更多对我们的讨论不重要的方法。


# #![allow(unused_variables)]
#fn main() {
pub trait Visitor<'de>: Sized {
type Value;
fn expecting(&self, formatter: &mut Formatter) -> Result;
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: Error,
{ }
...
}
#}

该特征的实现由解析器在内部用于构造结果类型。在我们的例子中,它看起来像这样:


# #![allow(unused_variables)]
#fn main() {
// chapter4/serde-custom/src/main.rs

// Implementing Deserialize for our struct defines how
// an instance of the struct should be created from an
// input stream of bytes

impl<'de> Deserialize<'de> for KubeConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
enum Field { Port, HealthzPort, MaxPods };

impl<'de> Deserialize<'de> for Field {

fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where D: Deserializer<'de>
{
struct FieldVisitor;

impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`port` or `healthz_port`or `max_pods`")
}

fn visit_str<E>(self, value: &str) -> Result<Field, E>
where E: de::Error {
match value {
"port" => Ok(Field::Port),
"healthz_port" => Ok(Field::HealthzPort),
"max_pods" => Ok(Field::MaxPods),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}

deserializer.deserialize_identifier(FieldVisitor)
}
}
}
#}

现在,反序列化器的输入是 json ,可以将其视为映射。因此,我们只需要实现访问者特征中的 visit_map 。如果任何非 json 数据传递给我们的反序列化器,它将在从该特征调用某些其他函数时出错。 以前的大多数实现都是样板。它归结为几个部分:为字段实现 Visitor ,并实现 visit_str (因为我们所有的字段都是字符串)。 此时,我们应该能够反序列化单个字段。第二部分是为整个结构实现 Visitor ,并实现 visit_map 。在所有情况下都必须适当处理错误。 最后,我们可以调用 deserializer.deserialize_struct 并传递结构的名称,字段列表以及整个结构的访问者实现。

具体实现如下:


# #![allow(unused_variables)]
#fn main() {
// chapter4/serde-custom/src/main.rs

impl<'de> Deserialize<'de> for KubeConfig {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 
        where D: Deserializer<'de> 
    {
        struct KubeConfigVisitor;

        impl<'de> Visitor<'de> for KubeConfigVisitor { 
            type Value = KubeConfig;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 
                formatter.write_str("struct KubeConfig") 
            }

            fn visit_map<V>(self, mut map: V) -> Result<KubeConfig, V::Error>
                where V: MapAccess<'de> 
            {
                let mut port = None;
                let mut hport = None;
                let mut max = None;

                while let Some(key) = map.next_key()? {
                    match key { 
                        Field::Port => { 
                            if port.is_some() { 
                                return Err(de::Error::duplicate_field("port")); 
                            } 
                            port = Some(map.next_value()?); 
                        } 
                        Field::HealthzPort => { 
                            if hport.is_some() { 
                                return Err(de::Error::duplicate_field ("healthz_port"));
                            } 
                            hport = Some(map.next_value()?); 
                        } 
                        Field::MaxPods => { 
                            if max.is_some() { 
                                return Err(de::Error::duplicate_field ("max_pods"));
                            } 
                            max = Some(map.next_value()?);
                        }
                    }
                } 
                let port = port.ok_or_else(|| de::Error::missing_field("port"))?;
                let hport = hport.ok_or_else(|| de::Error::missing_field("healthz_port"))?; 
                let max = max.ok_or_else(|| de::Error::missing_field("max_pods"))?; 
                Ok(KubeConfig {port: port, healthz_port: hport, max_pods: max})
            }
        }

        const FIELDS: &'static [&'static str] = &["port", "healthz_port", "max_pods"]; 
        deserializer.deserialize_struct("KubeConfig", FIELDS, KubeConfigVisitor)

    }
}
#}

Serde 还提供了一个 crate ,可用于使用类似令牌流的接口对自定义序列化器和反序列化器进行单元测试。要使用它,我们需要将 serde_test 添加到我们的 Cargo.toml 并在主文件中将其声明为 extern crate 。这是对我们的反序列化器的测试:


# #![allow(unused_variables)]
#fn main() {
// chapter4/serde-custom/src/main.rs

#[test] 
fn test_ser_de() { 
    let c = KubeConfig { port: 10, healthz_port: 11, max_pods: 12};

    assert_de_tokens(&c, &[
        Token::Struct { name: "KubeConfig", len: 3 }, 
        Token::Str("port"), Token::U8(10), 
        Token::Str("healthz_port"), 
        Token::U8(11), 
        Token::Str("max_pods"), 
        Token::U8(12), 
        Token::StructEnd,
    ]);
}
#}

assert_de_tokens 调用检查给定的标记流是否反序列化到我们的结构,从而测试我们的反序列化器。 我们还可以添加一个主函数来驱动序列化器,如下所示:

// chapter4/serde-custom/src/main.rs

fn main() { 
    let c = KubeConfig { port: 10, healthz_port: 11, max_pods: 12}; 
    let serialized = serde_json::to_string(&c).unwrap(); 
    println!("{:?}", serialized); 
}

在所有这些都可以使用 Cargo 运行。使用 cargo test 运行我们刚刚编写的测试,该测试应该通过。 货运应该运行 main 函数并打印序列化的json:


# #![allow(unused_variables)]
#fn main() {
$ cargo test
    Compiling serde-custom v0.1.0 (file:///serde-custom) 
     Finished dev [unoptimized + debuginfo] target(s) in 0.61 secs 
      Running target/debug/deps/serde_custom-81ee5105cf257563

running 1 test test 
test_ser_de ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out


$ cargo run
    Compiling serde-custom v0.1.0 (file:///serde-custom) 
     Finished dev [unoptimized + debuginfo] target(s) in 0.54 secs 
      Running `target/debug/serde-custom` 
"{\"port\":10,\"healthz_port\":11,\"max_pods\":12}"
#}