In order to read a JSON file, probably the best approach is to define a struct that will hold the content of the JSON file. Unfortunately it can be time consuming, so to get started you might want to read in the content of the JSON file and use the hand-picked values from the data.
In order to do this we'll use serde_json as you can see in the Cargo.toml
file:
examples/read-arbitrary-json/Cargo.toml
[package]
name = "read-arbitrary-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_json = "1.0"
This is the data file we use. It does not have any real meaning, it just contains all kinds of data types.
examples/read-arbitrary-json/data.json
{
"fname": "Foo",
"lname": "Bar",
"year": 1992,
"height": 178.2,
"married": true,
"numbers": [23, 19, 42],
"children": [
{
"name": "Alpha",
"birthdate": 2020
},
{
"name": "Beta",
"birthdate": 2022
}
]
}
The code:
examples/read-arbitrary-json/src/main.rs
use std::fs::File;
fn main() {
let filename = get_filename();
let data = match File::open(&filename) {
Ok(file) => {
let data: serde_json::Value =
serde_json::from_reader(&file).expect("JSON parsing error");
data
}
Err(error) => {
eprintln!("Error opening file {}: {}", filename, error);
std::process::exit(1);
}
};
dbg!(&data);
assert_eq!(data.get("fname").unwrap().as_str().unwrap(), "Foo");
assert_eq!(data["lname"].as_str().unwrap(), "Bar");
assert_eq!(data["height"].as_f64().unwrap(), 178.2);
assert_eq!(data["year"].as_u64().unwrap(), 1992);
assert_eq!(data["numbers"].as_array().unwrap().len(), 3);
assert_eq!(data["numbers"][0].as_u64().unwrap(), 23);
assert_eq!(data["children"].as_array().unwrap().len(), 2);
assert_eq!(data["children"][0]["name"].as_str().unwrap(), "Alpha");
}
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()
}
The program expects the name of the JSON file on the command line. We have a function called get_filename
that will
return the name of the file or will exit if the user did not provide a filename.
Then we open the JSON file and read in the content and convert it into a data-structure of type
Value using the
serde_json::from_reader function.
We assigned it to a variable called data
. When you work on real data, please try using a more descriptive name!
Then we see two ways to access the data one is calling the get method
on the data. This will return an Option so we need to unwrap
it to get the real value or we need to arrange some
more serious error handling if we are not sure the field "fname" exists.
data.get("fname").unwrap()
Alternatively we can access the data using square brackets:
data["lname"]
In either case we get another Value from which we can get the real value by using one of the as_
functions listed
for the Value.
In normal code you'd probably do something with the values these functions return, but for our demonstration I used the assert_eq! macro to compare the values to expected values.
Running the code
cargo run data.json
will produce this output:
[src/main.rs:17] &data = Object {
"children": Array [
Object {
"birthdate": Number(2020),
"name": String("Alpha"),
},
Object {
"birthdate": Number(2022),
"name": String("Beta"),
},
],
"fname": String("Foo"),
"height": Number(178.2),
"lname": String("Bar"),
"married": Bool(true),
"numbers": Array [
Number(23),
Number(19),
Number(42),
],
"year": Number(1992),
}
Conclusion
This way of reading a JSON file can be useful to get started, but we'll need a more robust way to verify the data and to make it easier to use the data once we read it in.