Most examples showing deserialization of JSON with Rust and serde_json
show deserialization into a known structure format, using a defined struct
.
Is it possible to deserialize a JSON document with an unknown structure into a generic Rust datatype which can handle this structural variance?
For example, I tried something like this
let filename = "example.json";
let maybe_ifile = std::fs::File::open(filename);
match maybe_ifile {
Ok(ifile) => {
let maybe_json_data = serde_json::from_reader::<_, BTreeMap<String, String>>(ifile);
match maybe_json_data {
Ok(json_data) => {
println!("{json_data:?}");
}
Err(error) => {
panic!("{error}")
}
}
}
Err(error) => {
panic!("{error}");
}
}
This works if the JSON document is structured as pairs of key-value strings, but it does not work for more general structures, so clearly using BTreeMap<String, String>
is not the right approach here.
What I am trying to achieve is flexibility using runtime / somewhat dynamic types.
This is possible in other languages, so I would assume there is a fairly trivial solution in Rust, I am just not aware of what this is.
The solution was to use the serde_json::Value
datatype. This provides a generic way to work with JSON data.
The single line changed:
let maybe_json_data = serde_json::from_reader::<_, serde_json::Value>(ifile);
Here's an example of deserializing a JSON string into a serde_json::Value
:
use serde_json::Value;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Example JSON string
let json_str = r#"
{
"name": "Alice",
"age": 30,
"is_member": true,
"favorites": {
"colors": ["red", "green", "blue"],
"numbers": [1, 2, 3]
}
}
"#;
// Parse the JSON string into a serde_json::Value
let json_value: Value = serde_json::from_str(json_str)?;
// Accessing elements in the JSON
if let Some(name) = json_value.get("name").and_then(|v| v.as_str()) {
println!("Name: {}", name);
}
if let Some(age) = json_value.get("age").and_then(|v| v.as_i64()) {
println!("Age: {}", age);
}
if let Some(is_member) = json_value.get("is_member").and_then(|v| v.as_bool()) {
println!("Is Member: {}", is_member);
}
// Nested JSON objects
if let Some(favorites) = json_value.get("favorites") {
if let Some(colors) = favorites.get("colors").and_then(|v| v.as_array()) {
println!("Favorite Colors: {:?}", colors);
}
}
Ok(())
}
Parsing JSON: The serde_json::from_str function parses the JSON string into a serde_json::Value.
Accessing Data: Use the get
method to access fields in the JSON. You can further use type-specific methods like as_str
, as_i64
, and as_bool
to convert the JSON values to Rust types.
The serde_json::Value
enum can represent JSON data of any structure:
Value::Object
for JSON objectsValue::Array
for arraysValue::String
for stringsValue::Number
for numbersValue::Bool
for booleansValue::Null
for nullsThis makes it suitable for generic JSON deserialization when the structure is not fixed.