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 = "2024"
[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?