Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize JSON in a recursive struct

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?

like image 589
qio Avatar asked Nov 24 '15 13:11

qio


1 Answers

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 Strings to Values, 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);
}
like image 109
euclio Avatar answered Sep 28 '22 05:09

euclio