Read simple JSON and deserialize into a struct

Deserializing a JSON string is not too hard especially if the JSON is simple and if we don't need all the fields.

  • 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:

{
    "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.

[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

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.

#![allow(unused)]
fn main() {
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.

#![allow(unused)]
fn main() {
#[derive(Deserialize, Debug)]
}

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

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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?