I have this JSON:
{
"argument0": {
"argument1": "test",
"argument2": {
"argument3": "test3"
}
}
}
I need to use some kind of recursive struct with methods like the HashMap<String, _>
in Rust. The key should always be a String
but the value can be a String
or the same Argument
struct.
#[derive(Clone, RustcDecodable, RustcEncodable)]
struct Argument {
key: String
value: String Or Argument
}
How can I achieve this?
You have a few distinct problems here.
First, you want to be able to define a data type that can be either one type or another type, but not both. This is what Rust's enum
data type is intended for.
enum Value {
String(String),
Argument(Argument),
}
This Value
type can contain either a String
or an Argument
, but not both.
Now, we need to define the Argument
type. In your example, an argument can contain arbitrary field names, so we can't just define a struct
. Instead, we can use a map collection from the standard library to map String
s to Value
s, such as BTreeMap
. We'll also define a type alias so that we can use the name Argument
instead of BTreeMap<String, Argument>
elsewhere in the program.
use std::collections::BTreeMap;
type Argument = BTreeMap<String, Argument>;
Now that we've successfully defined the type, let's define its serialization behavior using the serde library. Serde can automatically serialize types from the Rust standard library, and user structs can implement or derive the Serialize
and Deserialize
traits to add the functionality to their own types.
For most structs, we can just add a #[derive(Serialize)]
and/or #[derive(Deserialize)]
to implement the necessary traits for serialization. In this case, we want to customize the deserialization of our enum
to be untagged, so it just emits the value of the enum, not an object with "String" or "Argument" as the key. Instead, we just want the JSON to contain the value. We do this by adding a special attribute to the struct, #[serde(untagged)]
.
Here's a short Rust program that demonstrates the above concepts. This program will read your JSON example, and print the Debug
representation of a Rust type that represents the data.
#[macro_use]
extern crate serde_derive; // 1.0.78
extern crate serde; // 1.0.78
extern crate serde_json; // 1.0.27
use std::collections::BTreeMap;
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum Value {
String(String),
Argument(Argument),
}
type Argument = BTreeMap<String, Value>;
fn main() {
let argument: Argument = serde_json::from_str(
r#"{
"argument0": {
"argument1": "test",
"argument2": {
"argument3": "test3"
}
}
}"#,
).unwrap();
println!("{:?}", argument);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With