使用 Serde进入序列化和反序列化

Serde 是 Rust 中数据序列化和反序列化的事实标准方法。 Serde 支持许多数据结构,它可以开箱即用地串行化为多种给定的数据格式(包括 JSON 和 TOML ,CSV)。理解 Serde 的最简单方法是将其视为可逆函数,将给定的数据结构转换为字节流。除了标准数据类型之外,Serde 还提供了一些可以在用户定义的数据类型上实现的宏,使它们(可)序列化。

在第2章“Rust及其生态系统简介”中,我们讨论了如何使用过程宏来实现给定数据类型的自定义派生。 Serde 使用该机制提供两个自定义派生,名为 SerializeDeserialize ,可以为由 Serde 支持的数据类型组成的用户定义数据类型实现。让我们看一下这是如何工作的一个小例子。我们首先使用 Cargo 创建空项目:

Cargo.toml 如下所示:

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

[dependencies] 
serde = "1.0" 
serde_derive = "1.0" 
serde_json = "1.0" 
serde_yaml = "0.7.1"

serde crate 是 Serde 生态的核心。serde_derive crate 提供必要的工具,使用过程宏来派生 SerializeDeserialize 。接下来的两个 crate 分别为 JSON 和 YAML 提供特定于 Serde 的功能:

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

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;

// We will serialize and deserialize instances of
// this struct
#[derive(Serialize, Deserialize, Debug)]
struct ServerConfig {
    workers: u64,
    ignore: bool,
    auth_server: Option<String>,
}

fn main() {
    let config = ServerConfig {
        workers: 100,
        ignore: false,
        auth_server: Some("auth.server.io".to_string()),
    };

    {
        println!("To and from YAML");
        let serialized = serde_yaml::to_string(&config).unwrap();
        println!("{}", serialized);
        let deserialized: ServerConfig = serde_yaml::from_str(&serialized).unwrap();
        println!("{:?}", deserialized);
    }
    println!("\n\n");

    {
        println!("To and from JSON");
        let serialized = serde_json::to_string(&config).unwrap();
        println!("{}", serialized);
        let deserialized: ServerConfig = serde_json::from_str(&serialized).unwrap();
        println!("{:?}", deserialized);
    }
}


由于 serde_derive 导出宏,我们需要用 macro_use 声明对其标记;然后我们将所有依赖项声明为外部 crate . 设置完之后,我们就可以自定义数据类型。在这种情况下,我们感兴趣的是具有一堆不同类型参数的服务器的配置。 auth_server 参数是可选的,这就是它包含在 Option 中的原因。我们的结构派生了 Serde 的两个特征,以及编译器提供的 Debug 特性,稍后我们将在反序列化后显示它们。 在我们的 main 函数中,我们实例化我们的类并在其上调用 serde_yaml::to_string 以将其序列化为字符串;与此相反的是 serde_yaml::from_str

运行代码:

$ cargo run 
    Compiling serde-basic v0.1.0 (file:///Users/Abhishek/Desktop/rustbook/src/chapter4/serde-basic)
     Finished dev [unoptimized + debuginfo] target(s) in 1.88 secs
      Running `target/debug/serde-basic` 
To and from YAML 
--
workers: 100 
ignore: false 
auth_server: auth.server.io 
ServerConfig { workers: 100, ignore: false, auth_server: Some("auth.server.io") }

To and from JSON 
{"workers":100,"ignore":false,"auth_server":"auth.server.io"} 
ServerConfig { workers: 100, ignore: false, auth_server: Some("auth.server.io") }

让我们继续讨论通过网络使用 Serde 的更高级示例。在此示例中,我们将设置 TCP 服务器和客户端。这一部分与我们在上一章中所做的完全相同。但这一次,我们的 TCP 服务器将作为一个计算器,在三个空间中使用三个分量的三个分量接收一个点,并在同一参考帧中返回它与原点的距离。让我们像这样建立我们的 Cargo 项目:

$ cargo new --bin serde-server

Cargo.toml 如下:

$ cat Cargo.toml 
[package] 
name = "serde-server" 
version = "0.1.0" 
authors = ["Foo <foo@bar.com>"]

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

这样,我们就可以继续定义代码了。在本例中,服务器和客户机将在同一个二进制文件中。 应用程序将接受一个标志,该标志指示它应该作为服务器还是客户机运行。 正如我们在上一章中所做的,在服务器的情况下,我们将绑定到已知端口上的所有本地接口,并监听传入的连接。客户机案例将连接到该已知端口上的服务器,并等待控制台上的用户输入。 客户机期望输入为三个整数的逗号分隔列表,每个轴一个。在获取输入时,客户机构造一个给定定义的结构,使用 serde 对其进行序列化,并将字节流发送到服务器。 服务器将流反序列化为同一类型的结构。然后,它计算距离并返回结果,然后客户机显示该结果。代码如下:

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

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::net::{TcpListener, TcpStream};
use std::io::{stdin, BufRead, BufReader, Error, Write};
use std::{env, str, thread};

#[derive(Serialize, Deserialize, Debug)]
struct Point3D {
    x: u32,
    y: u32,
    z: u32,
}

fn handle_client(stream: TcpStream) -> Result<(), Error> {
    println!("Incoming connection from: {}", stream.peer_addr()?);
    let mut data = Vec::new();
    let mut stream = BufReader::new(stream);

    loop {
        data.clear();

        let bytes_read = stream.read_until(b'\n', &mut data)?;
        if bytes_read == 0 {
            return Ok(());
        }
        let input: Point3D = serde_json::from_slice(&data)?;
        let value = input.x.pow(2) + input.y.pow(2) + input.z.pow(2);

        write!(stream.get_mut(), "{}", f64::from(value).sqrt())?;
        write!(stream.get_mut(), "{}", "\n")?;

    }
}

fn main() {

    let args: Vec<_> = env::args().collect();

    if args.len() != 2 {
        eprintln!("Please provide --client or --server as argument");
        std::process::exit(1);
    }

    if args[1] == "--server" {
        let listener = TcpListener::bind("0.0.0.0:8888").expect("Could not bind");
        for stream in listener.incoming() {
            match stream {
                Err(e) => eprintln!("failed: {}", e),
                Ok(stream) => {
                    thread::spawn(move || {
                        handle_client(stream).unwrap_or_else(|error| eprintln!("{:?}", error));
                    });
                }
            }
        }
    } else if args[1] == "--client" {
        let mut stream = TcpStream::connect("127.0.0.1:8888").expect("Could not connect to server");
        println!("Please provide a 3D point as three comma separated integers");
        loop {
            let mut input = String::new();
            let mut buffer: Vec<u8> = Vec::new();
            stdin().read_line(&mut input).expect("Failed to read from stdin");
            let parts: Vec<&str> = input.trim_matches('\n').split(',').collect();
            let point = Point3D {
                x: parts[0].parse().unwrap(),
                y: parts[1].parse().unwrap(),
                z: parts[2].parse().unwrap(),

            };

            stream.write_all(serde_json::to_string(&point).unwrap().as_bytes())
                .expect("Failed to write to server");
            stream.write_all(b"\n").expect("Failed to write to server");

            let mut reader = BufReader::new(&stream);
            reader.read_until(b'\n', &mut buffer).expect("Could not read into buffer");
            let input = str::from_utf8(&buffer).expect("Could not write buffer as string");
            if input == "" {
                eprintln!("Empty response from server");
            }
            print!("Response from server {}", input);

        }
    }
}







我们设置 Serde ,就像我们在上一个例子中所做的那样。然后,我们将 3D 点定义为三个元素的结构。 在我们的 main 函数中,我们处理 CLI 参数并分支到客户端或服务器,具体取决于传递的内容。 在这两种情况下,我们通过发送换行符来表示传输结束。客户端从 stdin 读取一行,清除它,并在循环中创建结构的实例。 在这两种情况下,我们将流包装在 BufReader 中以便于处理。我们使用 Cargo 运行代码。服务器上的示例会话如下:

server$ cargo run -- --server 
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs 
     Running `target/debug/serde-server --server` 
Incoming connection from: 127.0.0.1:49630

在客户端,我们看到以下与服务器的交互。正如所料,客户端读取输入,序列化输入,并将其发送到服务器。 然后它等待响应,当它得到响应时,将结果打印到标准输出:

client$ cargo run -- --client 
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs 
     Running `target/debug/serde-server --client` 
Please provide a 3D point as three comma separated integers 
1,2,3 
Response from server 3.7416573867739413 
3,4,5 
Response from server 7.0710678118654755 
4,5,6
Response from server 8.774964387392123