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?