青蛙小白
目录

serde_json

cargo add serde_json

在Rust中,可能会遇到三种常见的需要处理JSON数据的方式:

  • 文本数据: 在HTTP端点接收到的、从文件中读取的、或准备发送到远程服务器的未经处理的JSON数据字符串。

  • 无类型或松散类型的表示 想在传递某些JSON数据之前验证其有效性,但不需要知道它包含的具体结构。或者想进行一些非常基础的操作,比如在特定位置插入一个键。

  • 强类型的Rust数据结构。 期望所有或大部分数据符合特定结构,并且想要在避免JSON松散特性带来麻烦的情况下完成实际工作。

Serde JSON提供了高效、灵活、安全的方法来在这些表示方式之间转换数据。

无类型或松散类型的表示

任何有效的JSON数据都可以在以下递归枚举表示中进行操作。这个数据结构是serde_json::Value

enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}

JSON数据字符串可以通过 serde_json::from_str 函数解析成 serde_json::Value。此外还有 from_slice 用于从字节切片 &[u8] 解析,以及 from_reader 用于从任何 io::Read(比如文件或 TCP 流)解析。

use serde_json::{Result, Value};

fn untyped_example() -> Result<()> {
    // Some JSON input data as a &str. Maybe this comes from the user.
    let data = r#"
        {
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;

    // Parse the string of data into serde_json::Value.
    let v: Value = serde_json::from_str(data)?;

    // Access parts of the data by indexing with square brackets.
    println!("Please call {} at the number {}", v["name"], v["phones"][0]);

    Ok(())
}

索引操作如 v["name"] 的结果是该索引处数据的借用,因此其类型是 &Value。JSON 映射可以用字符串键进行索引,而 JSON 数组可以用整数键进行索引。如果数据类型与索引的类型不匹配,或者映射中不包含所索引的键,或者vector的索引超出范围,则返回的元素是 Value::Null

当打印 Value 时,它会被打印为 JSON 字符串。因此在上述代码中,输出看起来像 Please call "John Doe" at the number "+44 1234567"。这里出现引号是因为 v["name"] 是一个包含 JSON 字符串的 &Value,其 JSON 表示是 "John Doe"。如果要打印不带引号的纯字符串,需要使用 as_str() 将 JSON 字符串转换为 Rust 字符串,或者按照下一节所述避免使用 Value

Value 表示方式对于非常基础的任务来说是足够的,但对于更重要的任务来说可能会很繁琐。例如,要正确实现错误处理会变得很冗长,比如试图检测输入数据中是否存在未识别的字段。当你犯错时编译器也无法帮助你,例如在代码中的几十个地方中,将 v["name"] 错误地写成 v["nmae"]

将JSON解析为强类型数据结构

Serde提供了一种强大的方法,能够将JSON数据基本自动地映射到Rust数据结构中。


#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

fn typed_example() -> Result<()> {
    // Some JSON input data as a &str. Maybe this comes from the user.
    let data = r#"
        {
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;

    // Parse the string of data into a Person object. This is exactly the
    // same function as the one that produced serde_json::Value above, but
    // now we are asking it for a Person as output.
    let p: Person = serde_json::from_str(data)?;

    // Do things just like with any other Rust data structure.
    println!("Please call {} at the number {}", p.name, p.phones[0]);

    Ok(())
}

Serde通过serde_json::from_str函数实现与之前相同的功能,但这次我们将返回值赋给一个Person类型的变量,这样Serde就会自动将输入数据解析为Person类型,并在数据结构与预期的Person格式不符时生成详细的错误信息。

任何实现了Serde的Deserialize特征的类型都可以通过这种方式进行反序列化。这包括Rust标准库中的内置类型,如Vec<T>HashMap<K, V>,以及任何使用#[derive(Deserialize)]标注的结构体或枚举。

一旦我们得到了Person类型的变量p,我们的IDE和Rust编译器就能像处理其他Rust代码一样帮助我们正确使用它。IDE可以自动补全字段名称以防止拼写错误,这在serde_json::Value表示方式中是不可能实现的。而且Rust编译器可以检查当我们写p.phones[0]时,p.phones一定是Vec<String>类型,所以对其进行索引是有意义的,并且会产生一个String类型的值。

构造JSON值(serde_json::Value)

Serde JSON提供了一个json!宏,可以使用非常自然的JSON语法来构建serde_json::Value对象。

use serde_json::json;

fn main() {
    // The type of `john` is `serde_json::Value`
    let john = json!({
        "name": "John Doe",
        "age": 43,
        "phones": [
            "+44 1234567",
            "+44 2345678"
        ]
    });

    println!("first phone number: {}", john["phones"][0]);

    // Convert to a string of JSON and print it out
    println!("{}", john.to_string());
}

Value::to_string()函数可以将serde_json::Value转换为JSON格式的String文本。

json!宏的一个巧妙之处在于,在构建JSON值时可以直接插入变量和表达式。Serde会在编译时检查你插入的值是否可以表示为JSON。

let full_name = "John Doe";
let age_last_year = 42;

// The type of `john` is `serde_json::Value`
let john = json!({
    "name": full_name,
    "age": age_last_year + 1,
    "phones": [
        format!("+44 {}", random_phone())
    ]
});

这种方式非常方便,但我们遇到了之前使用Value时的同样问题:如果我们出错了,IDE和Rust编译器无法帮助我们。Serde JSON提供了一种更好的方式来将强类型数据结构序列化为JSON文本。

通过序列化数据结构创建JSON

数据结构可以通过serde_json::to_string转换为JSON字符串。此外还有serde_json::to_vec可以序列化为Vec<u8>,以及serde_json::to_writer可以序列化到任何io::Write类型,比如文件或TCP流。

use serde::{Deserialize, Serialize};
use serde_json::Result;

#[derive(Serialize, Deserialize)]
struct Address {
    street: String,
    city: String,
}

fn print_an_address() -> Result<()> {
    // Some data structure.
    let address = Address {
        street: "10 Downing Street".to_owned(),
        city: "London".to_owned(),
    };

    // Serialize it to a JSON string.
    let j = serde_json::to_string(&address)?;

    // Print, write to a file, or send to an HTTP server.
    println!("{}", j);

    Ok(())
}

任何实现了Serde的Serialize特征的类型都可以通过这种方式进行序列化。这包括Rust标准库中的内置类型,如Vec<T>HashMap<K, V>,以及任何使用#[derive(Serialize)]标注的结构体或枚举。

评论