前言
无法想象JavaScript
没有JSON
的样子,不过对于Rust
这种类型化语言来说会有所区别,我们需要将JSON
转化成JavaScript
中的对象,转化成Rust
中的结构体
正文
serde
serde (Serialization 和 Deserialization 的简写)是一个很神奇的包,仅需要一行代码就可以将你的数据结构转化成JSON
格式。该包提供了Serialize
和Deserialize
两个trait
,可以让你自己来定义如何将一种数据结构转化成JSON
。它本身不做转换的实际工作,实际工作是其它包实现的。我们之前已经用到了一个类似的包rmp-serde
,它会将数据编码成MessagePack
格式。我们本身不需要对serde
有过深的了解,因为这里用到的都是通用的数据结构,如果你想有点新创造,那你需要实现上述的trait
利用derive
功能,serde
使得数据转化变的十分简单,你可以通过Serialize
和Deserialize
对大多数对象进行自动转化
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Author {
first: String,
last: String,
}
一旦你实现了上述trait
中的一个或两个,你就拥有了对任意格式数据的自动支持
下面这段代码用serde_json
和rmp-serde
将一种数据格式转换成JSON
和MessagePack
格式
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Author {
first: String,
last: String,
}
fn main() {
let mark_twain = Author {
first: "Samuel".to_owned(),
last: "Clemens".to_owned(),
};
let serialized_json = serde_json::to_string(&mark_twain).unwrap();
println!("Serialized as JSON: {}", serialized_json);
let serialized_mp = rmp_serde::to_vec(&mark_twain).unwrap();
println!("Serialized as MessagePack: {:?}", serialized_mp);
let deserialized_json: Author = serde_json::from_str(&serialized_json).unwrap();
println!("Deserialized from JSON: {:?}", deserialized_json);
let deserialized_mp: Author = rmp_serde::from_read_ref(&serialized_mp).unwrap();
println!("Deserialized from MessagePack: {:?}", deserialized_mp);
}
输出:
$ cargo run -p day-22-serde
[snipped]
Serialized as JSON: {"first":"Samuel","last":"Clemens"}
Serialized as MessagePack: [146, 166, 83, 97, 109, 117, 101, 108, 167, 67, 108, 101, 109, 101, 110, 115]
Deserialized from JSON: Author { first: "Samuel", last: "Clemens" }
Deserialized from MessagePack: Author { first: "Samuel", last: "Clemens" }
注意:留意第20行和第22行中如何显式指定反序列化的类型。这是反序列化器知道要输出什么内容的唯一方法
拓展命令行程序
上一篇文章中我们用到的命令行对于输入的内容限制较大,只能输入字符串,今天我们对其进行一下拓展,让它可以接收JSON
数据
在Cargo.toml
文件中添加serde_json
,在这里我们不需要serde
crates/cli/Cargo.toml
[dependencies]
my-lib = { path = "../my-lib" }
log = "0.4"
env_logger = "0.9"
structopt = "0.3"
rmp-serde = "0.15"
anyhow = "1.0"
serde_json = "1.0"
JSON
当我们确定自己要表达的内容时,采用自定义结构体是很不错的选择,但并不是每次都知道的这么清楚,所以我们需要一个中介,进行一种更加泛化的表示。serde_json
的内置JSON
表示可以通过serde_json::Value
枚举值得到,而不必创建一个有derive
标记的自定义结构体。这样的好处是可以不必知晓JSON
的具体结构,用一种更加通用的方式来进行表达
现在我们改写下命令行参数的配置,让其接收一个JSON
文件的路径
crates/cli/src/main.rs
struct CliOptions {
/// The WebAssembly file to load.
#[structopt(parse(from_os_str))]
pub(crate) file_path: PathBuf,
/// The operation to invoke in the WASM file.
#[structopt()]
pub(crate) operation: String,
/// The path to the JSON data to use as input.
#[structopt(parse(from_os_str))]
pub(crate) json_path: PathBuf,
}
现在我们得到了一个JSON
文件路径,我们还需去读取它的内容。我们用fs::read
方法将WASM
文件以字节的方式读取,我们还可以用fs::read_to_string
方法将文件以字符串的方式读取
crates/cli/src/main.rs
fn run(options: CliOptions) -> anyhow::Result<String> {
// snipped
let json = fs::read_to_string(options.json_path)?;
// snipped
}
然后我们用serde_json::from_str
方法将字符串解析为JSON
crates/cli/src/main.rs
fn run(options: CliOptions) -> anyhow::Result<String> {
// snipped
let json = fs::read_to_string(options.json_path)?;
let data: serde_json::Value = serde_json::from_str(&json)?;
debug!("Data: {:?}", data);
// snipped
}
最后我们将返回值的数据类型改为serde_json::Value
以便可以将结果以JSON
形式来表达
crates/cli/src/main.rs
fn run(options: CliOptions) -> anyhow::Result<serde_json::Value> {
let module = Module::from_file(&options.file_path)?;
info!("Module loaded");
let json = fs::read_to_string(options.json_path)?;
let data: serde_json::Value = serde_json::from_str(&json)?;
debug!("Data: {:?}", data);
let bytes = rmp_serde::to_vec(&data)?;
debug!("Running {} with payload: {:?}", options.operation, bytes);
let result = module.run(&options.operation, &bytes)?;
let unpacked: serde_json::Value = rmp_serde::from_read_ref(&result)?;
Ok(unpacked)
}
现在我们可以将输入存放在JSON
文件中来运行我们的命令行程序了
cargo run -p cli -- crates/my-lib/tests/test.wasm hello hello.json
[snipped]
"Hello, Potter."
我们的命令行程序越来越实用了,现在可以尝试起个更有意义的名字,这里我们将其改名为wapc-runner
我们之前一直以debug
形式来打包,现在用release
模式运行下看看有什么不同
$ cargo build --release
[snipped]
Finished release [optimized] target(s) in 6m 08s
$ cp ./target/release/wapc-runner .
$ ./wapc-runner ./blog.wasm render ./blog.json
"<html><head><title>The Adventures of Tom Sawyer</title></head><body><h1>The Adventures of Tom Sawyer</h1><h2>By Mark Twain</h2><p>“TOM!”\n\nNo answer.\n\n“TOM!”\n\nNo answer.\n\n“What’s gone with that boy, I wonder? You TOM!”\n\nNo answer.</p></body></html>"
release
模式所需的时间可能会更长,具体时间取决于你的机器
现在我们有了一个很棒的可移植WebAssembly
运行器,如果你希望继续对其优化,这里有个建议:你可以判断在JSON
文件参数缺失的情况下直接从STDIN
读取JSON
数据,atty
包可以帮助到你判断是否在终端运行
相关阅读
总结
本教程已经接近尾声,后面我们将会进行一些总结,包括介绍一些有用的包