Read simple JSON and deserialize into a struct

JSON Deserialize serde serde_json Debug dbg! assert! assert_eq!

In another post we saw how to process arbitrary JSON string without defining anything about it and without creating a struct. It can be a good idea when we start experimenting with a new JSON structure, but eventually we will want to create a struct and deserialize the JSON string into this struct.

In this example we start on that journey for a simple JSON File that looks like this:

examples/read-simple-json/data.json

{
    "fname": "Foo",
    "lname": "Bar",
    "year": 2023,
    "height": 6.1,
    "married": true
}

We need both serde and serde_json

For this we'll need both serde_json and serde with the derive feature.

examples/read-simple-json/Cargo.toml

[package]
name = "read-simple-json"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

The code

examples/read-simple-json/src/main.rs

use std::fs::File;
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Person {
    fname: String,
    married: bool,
}

fn main() {
    let filename = get_filename();

    let data: Person = match File::open(&filename) {
        Ok(file) => {
             serde_json::from_reader(&file).expect("JSON parsing error")
        },
        Err(error) => {
            eprintln!("Error opening file {}: {}", filename, error);
            std::process::exit(1);
        },
    };
    dbg!(&data);
    assert_eq!(data.fname, "Foo");
    assert!(data.married);
}


fn get_filename() -> String {
    let args: Vec<String> = std::env::args().collect();
    if args.len() != 2 {
        eprintln!("Usage: {} FILENAME", args[0]);
        std::process::exit(1);
    }
    args[1].to_string()
}



We need to create a struct to represent the data where we define the expected fields and their type.

As our real data does not have a lot of fields we could have created a struct defining all the fields, but I wanted to show the process you might take if you have a bigger JSON file and don't want to do all the work up-front, or if you don't actually need all the fields from the JSON string.

struct Person {
    fname: String,
    married: bool,
}

We need to add the Deserialize trait to it. I also included the Debug trait to allow us to use the dbg! macro to display the content of the struct.

#[derive(Deserialize, Debug)]

The we open the file and use the serde_json::from_reader function to read in the file.

serde_json::from_reader(&file).expect("JSON parsing error")

The important part that differs from the generic JSON parsing example is that we read the data into a variable that was annotated with the Person struct:

let data: Person =

The we can use the dbg! macro to show the content of the struct. We also use the assert_eq! to verify the value of a string and the assert! macro to verify the value of a bool (boolean) field.

Conclusion

It is quite easy to get started deserializing a simple JSON file, especially if we don't need to get all the fields right at the beginning.

There are, however many more aspect of JSON we need to deal with.

  • How to handle more complex JSON structures?

  • How to handle a JSON that has a list (array) at the root?

  • How can we make sure we mapped all the fields?

  • What to do if a field we added to the struct is missing from the JSON?

  • What to do if there is a typo in the fields of the JSON?

Related Pages

JSON and Rust

Author

Gabor Szabo (szabgab)

Gabor Szabo, the author of the Rust Maven web site maintains several Open source projects in Rust and while he still feels he has tons of new things to learn about Rust he already offers training courses in Rust and still teaches Python, Perl, git, GitHub, GitLab, CI, and testing.

Gabor Szabo