Structs from json

Trying to create structs to read with serde_json. They have dynamic keys, which I understood to be solved with a HashMap<String, Struct> and optional fields which I solved with Options. Although the actual json is more complicated, I distilled a minor use case.

use serde::{Deserialize, Serialize};
use serde_json::{Result, json};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Debug)]
pub struct Test {
    pub field_1: Option<String>,
    pub field_2: Vec<HashMap<String, TestTwo>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TestTwo {
    pub field_3: String,
}

pub fn read_json() -> Result<Test> {

    let json = json!({
        "field_2": {"TestTwo 1": [{"field_3":"this is field 3"}],
                    "TestTwo 2": [{"field_3": "this is another field 3"}]}
    }).to_string();

    let json: Test = serde_json::from_str(&json)?;
    Ok(json)
}

fn main() {
    let json = read_json().unwrap();
    println!("{:?}", json);
}

Obviously my structs have been wrongly constructed but I can't figure out what went wrong with the HashMaps and Vecs.

Your Test::field_2 is declared to have type Vec<_> but you are providing a map/object in the JSON. That's exactly what the error message from Serde is saying. Also, your inner fields are arrays/vectors whereas they shouldn't be. You basically transposed the vec and the hash map. This works.

When constructing JSON that successfully deserializes into a given type, there's no magic – you have to follow the tree of types level by level, field by field, and emit equivalent JSON types. It is as simple as that.

2 Likes

Thanks so much for your help. I am afraid you misunderstood the problem. I am trying to create structs to read this json, not the other way round. As I said: my structs are wrongly constructed. How do I write these map/objects?

Do the transposition in the struct rather than the JSON.

  #[derive(Serialize, Deserialize, Debug)]
  pub struct Test {
      pub field_1: Option<String>,
-     pub field_2: Vec<HashMap<String, TestTwo>>,
+     pub field_2: HashMap<String, Vec<TestTwo>>,
  }
1 Like
#[derive(Serialize, Deserialize, Debug)]
pub struct Test {
     pub field_1: Option<String>,
-    pub field_2: Vec<HashMap<String, TestTwo>>,
+    pub field_2: HashMap<String, Vec<TestTwo>>,
}
1 Like

:smile:

1 Like

Thanks. It still panics when I try.

Works for me.

1 Like

No it doesn't panic. You must be doing something else wrong too.

2 Likes

Sorry. It indeed does. Thanks very much.

Quite.

I added a field and it stopped working again for me. Does it work for you?

You'd have to make field_3 optional in TestTwo if you want to omit it (what you do in the second element of the vector you pass as value to "TestTwo 1").

Sorry, don't follow. Field_3 is not optional. I made field_4 optional and omitted it.

Then your JSON is off and should be:

    let json = json!({
-        "field_2": {"TestTwo 1": [{"field_3":"this is field 3"}, {"field_4": "this is field 4"}],
+        "field_2": {"TestTwo 1": [{"field_3":"this is field 3", "field_4": "this is field 4"}],
                    "TestTwo 2": [{"field_3": "this is another field 3"}]}
    }).to_string();

I thought you meant to pass two instances of TestTwo in the first vector.

Well, this is actually what my json is. It is even a bit more elaborate but I am adding bits for testing. If the json was constructed as H2CO3 suggested in his first reply, it would have made things much easier, I suppose.

Then you have two elements passed in your vector, the first missing field_4 and the second one is missing field_3. If we take the JSON as given, I'd say field_3 should be Option<String> instead of String.

Yes, although I don't quite understand it, that seems to be working.

The problem is the second object in "TestTwo 1": {"field_4": "this is field 4"}

Your original definition

#[derive(Serialize, Deserialize, Debug)]
pub struct TestTwo {
    pub field_3: String,
    pub field_4: Option<String>,
}

stated that all TestTwo objects MUST have a field_3. And this one failed to deserialize, because it didn't.

Changing the struct definition to field_3: Option<String> means that field_3 does not need to be present in the JSON, which allows it to parse.

1 Like

There still remains one problem in my actual json case. It turns out that one field can be either a u64 or vec<u64>. Is that solvable?