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?