24 days from Node.js to Rust
preface
It’s hard to imagine JavaScript without JSON, but it makes a difference for a typed language like Rust, where we need to convert JSON into objects in JavaScript, into structures in Rust
The body of the
serde
Serde (short for Serialization and Deserialization) is an amazing package that converts your data structure to JSON format with just one line of code. The package provides traits Serialize and Deserialize that let you define how to convert a data structure to JSON. It does not do the actual work of the transformation itself; the actual work is done by other packages. We’ve already used a similar package, RMP-serde, which encodes data in MessagePack format. We don’t need to know much about Serde itself, because it’s all about generic data structures, and if you want to get creative, you’ll need to implement the traits described above
With derive, Serde makes converting data very easy, and you can automatically convert most objects using Serialize and Deserialize
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Author {
first: String,
last: String,}Copy the code
Once you implement one or both of these traits, you have automatic support for data in any format
The following code converts one data format to JSON and MessagePack format using serde_JSON and RMP-serde
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);
}
Copy the code
Output:
$ 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" }
Copy the code
Note: Notice how the deserialization type is explicitly specified in lines 20 and 22. That’s the only way the deserializer knows what to output, right
Expand the command line program
The command line we used in the last article was very limited in what you can input, except strings. Today we’ll extend it to accept JSON data
Add serde_JSON to the Cargo. Toml file. We don’t need serde here
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"
Copy the code
JSON
It’s good to use a custom structure when we’re sure what we want to express, but it’s not always clear, so we need an intermediary to make a more general representation. The built-in JSON representation of SERde_json can be accessed through the Serde_json ::Value enumeration without having to create a custom structure with the derive tag. This has the advantage of being able to express JSON in a more general way without knowing the exact structure
Now let’s rewrite the configuration of the command line argument to receive the path to a JSON file
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,
}
Copy the code
Now that we have a JSON file path, we need to read its contents. We read WASM files as bytes using the fs::read method. We can also read files as strings using the fs::read_to_string method
crates/cli/src/main.rs
fn run(options: CliOptions) -> anyhow::Result<String> {
// snipped
letjson = fs::read_to_string(options.json_path)? ;// snipped
}
Copy the code
We then parse the string into JSON using the serde_json::from_str method
crates/cli/src/main.rs
fn run(options: CliOptions) -> anyhow::Result<String> {
// snipped
letjson = fs::read_to_string(options.json_path)? ;letdata: serde_json::Value = serde_json::from_str(&json)? ; debug! ("Data: {:? }", data);
// snipped
}
Copy the code
Finally, we change the data type of the return Value to serde_json::Value so that the result can be expressed as JSON
crates/cli/src/main.rs
fn run(options: CliOptions) -> anyhow::Result<serde_json::Value> {
letmodule = Module::from_file(&options.file_path)? ; info! ("Module loaded");
letjson = fs::read_to_string(options.json_path)? ;letdata: serde_json::Value = serde_json::from_str(&json)? ; debug! ("Data: {:? }", data);
letbytes = rmp_serde::to_vec(&data)? ; debug! ("Running {} with payload: {:?}", options.operation, bytes);
let result = module.run(&options.operation, &bytes)?;
letunpacked: serde_json::Value = rmp_serde::from_read_ref(&result)? ;Ok(unpacked)
}
Copy the code
Now we can put the input in a JSON file to run our command-line program
cargo run -p cli -- crates/my-lib/tests/test.wasm hello hello.json
[snipped]
"Hello, Potter."
Copy the code
Our command line program is getting more useful, and now we can try a more meaningful name, which we’ll rename to Wapc-Runner
We used to package it in debug mode, now let’s run it in Release mode and see what the difference is
$ cargo build --release
[snipped]
Finished release [optimized] target(s) in 6m 08s
$ cp ./target/release/wapc-runner .
$ ./wapc-runner ./blog.wasm render ./blog.json
"The Adventures of Tom Sawyer The Adventures of Tom Sawyer
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.
"
Copy the code
Release mode may take longer, depending on your machine
Now that we have a great portable WebAssembly runner, here’s a suggestion if you want to continue optimizing it: You can read JSON data directly from STDIN in the absence of JSON file parameters, and the ATTY package helps you determine if it’s running on a terminal
reading
serde
serde_json
rmp-serde
handlebars
The Adventures of Tom Sawyer
conclusion
We are nearing the end of this tutorial, and we will conclude with some useful packages