Intro

In this post, I will show you how to read and write JSON data with Rust using the wonderful serde library.

Software

The following software was used in this post.

  • Rust - 1.59.0
  • serde - 1.0.136
  • serde_derive - 1.0.136
  • serde_json - 1.0.79

Dependencies

Add the following libraries to the dependencies section of the cargo.toml file.

Cargo.toml
[dependencies]
serde = "1.0.136"
serde_derive = "1.0.136"
serde_json = "1.0.79"

Unknown JSON Model

When you have a JSON data structure with an unknown data model, you can deserialize the data into a serde_json::Value. But Rust is a strongly typed language, what is the sorcery? serde_json::Value is an enum with variants that match all valid JSON data types. It's used to automagically map JSON data to something that Rust can understand.

Feast your eyes on the wizardry of the serde_json::Value enum young grasshopper.

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

The following example uses serde_json::Value to deserialize JSON data.

main.rs
use serde_json::Value;
use std::process::exit;

fn main() {
    // Some JSON input data as a &str.
    let data = r#"
        {
            "hostname": "rt01",
            "ipv4_addresses": [
                "10.1.1.1/24",
                "172.16.1.1/24"
            ]
        }"#;

    // Parse the string of data into serde_json::Value.
    let json_data: Value = match serde_json::from_str(data) {
        Ok(v) => v,
        Err(_) => {
            // Write `error` to `stderr`.
            eprintln!("Unable to load data");
            // Exit the program with exit code `1`.
            exit(1);
        }
    };

    // Access data fields with square brackets [] syntax.
    println!(
        "hostname {} has these IPv4 addresses {}",
        json_data["hostname"], json_data["ipv4_addresses"]
    );
    // => hostname "rt01" has these IPv4 addresses ["10.1.1.1/24","172.16.1.1/24"]
}

Structured JSON Model

When you know the shape of the JSON data model you can deserialize it into a struct(s) to gain the type safety guarantees of Rust and the benefits of using a struct as a data container.

main.rs
use serde_derive::Deserialize;
use std::process::exit;

// Annotate the struct with the Deserialize macro.
#[derive(Deserialize)]
struct NetDevice {
    hostname: String,
    ipv4_addresses: Vec,
}

fn main() {
    // The same JSON input data from the previous example.
    let data = r#"
        {
            "hostname": "rt01",
            "ipv4_addresses": [
                "10.1.1.1/24",
                "172.16.1.1/24"
            ]
        }"#;

    // Parse the string of data into a NetDevice struct. This is exactly the
    // same function as the one that produced serde_json::Value above, but
    // now we are asking it for a NetDevice as output.
    let netdev: NetDevice = match serde_json::from_str(data) {
        Ok(v) => v,
        Err(_) => {
            // Write `msg` to `stderr`.
            eprintln!("Unable to load data");
            // Exit the program with exit code `1`.
            exit(1);
        }
    };

    // Access struct fields with the dot (.) operator.
    println!(
        "hostname {} has these IPv4 addresses {}",
        netdev.hostname,
        netdev.ipv4_addresses.join(", ")
    );
    //  => hostname rt01 has these IPv4 addresses 10.1.1.1/24, 172.16.1.1/24
}

Struct to JSON

Now, lets take the NetDevice struct and serialize it to a valid JSON data structure.

main.rs
use serde_derive::Serialize;
use std::process::exit;

// Annotate the struct with the Serialize macro.
#[derive(Serialize)]
struct NetDevice {
    hostname: String,
    ipv4_addresses: Vec,
}

fn main() {
    // Instantiate a NetDevice object.
    let netdev = NetDevice {
        hostname: "rt01".to_string(),
        ipv4_addresses: vec!["10.1.1.1/24".to_string(), "172.16.1.1/24".to_string()],
    };

    // Serialize it to a JSON string.
    let j = match serde_json::to_string(&netdev) {
        Ok(v) => v,
        Err(_) => {
            // Write `msg` to `stderr`.
            eprintln!("Unable to load data");
            // Exit the program with exit code `1`.
            exit(1);
        }
    };

    // Now you can print, write to a file, or send JSON to a HTTP server.
    // In our case we are just printing out the JSON data.
    println!("{}", j);
    // => {"hostname":"rt01","ipv4_addresses":["10.1.1.1/24","172.16.1.1/24"]}
}

Outro

In this post, I showed you how to read and write JSON data with Rust. Thanks to the marvelous serde library, the process is relatively painless.

Thanks for tuning in you beautiful hooman, I hope you have a wonderful day ✌️

Tags

rust