Search code examples
rustserde-json

How to deserialize nested struct?


Assuming the following JSON should be read:

let json = r#"{
    "scjson": [
        { "StateMachine": { "id": "sm_1" } },
        { "StateMachine": { "id": "sm_2" } }
    ]
}"#;

In words: An array of StateMachine, with each StateMachine has a field "id" from type string.

How can I deserialize this with serde? I tried:

#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct StateMachine {
    id: String,
}

#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Scjson {
    scjson: Vec<StateMachine>,
}

But the ID is never deserialized.

In the end, I would like to parse:

scjson:
  - StateMachine:
      id: "sm_1"
      states:
        - AtomicState:
            id: atomic_1
        - AtomicState:
            id: atomic_2
            transitions:
              - Transition: { event: "E1", executable_content: "asdf" }
        - ParallelState:
            InitialTransition: { }
        - CompoundState:
            id: compound_1
            initialTransition: { event: "E2", condition: "some condition" }
  - StateMachine:
      id: "sm_2"
      states:
        - FinalState:
            id: "asdf"
            onEntry: "17"

Solution

  • You are missing one layer of indirection. The scjson key contains a list of YAML dictionaries, where each dictionary has a single key StateMachine, and its value is yet another dictionary with one key id.

    Here's the fixed version:

    use serde::{Deserialize, Serialize};
    use serde_yaml;
    
    #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
    pub struct StateMachine {
        id: String,
    }
    
    #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
    pub struct Scjson {
        scjson: Vec<ScjsonElement>,
    }
    
    #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
    pub struct ScjsonElement {
        StateMachine: StateMachine,
    }
    
    fn main() {
        let message = r#"
    scjson:
      - StateMachine:
          id: "sm_1"
      - StateMachine:
          id: "sm_2"
        "#;
    
        let result: Scjson = serde_yaml::from_str(message).unwrap();
        println!("{:?}", result)
    }
    

    This is the straightforward solution, but it seems that whatever produces the YAML/JSON uses StateMachine and similar keys to encode the element type. If that is the case, then enum is the proper answer:

    use serde::{Deserialize, Serialize};
    use serde_yaml;
    
    #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
    pub struct StateMachine {
        id: String,
    }
    
    #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
    pub struct Scjson {
        scjson: Vec<ScjsonElement>,
    }
    
    #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
    pub enum ScjsonElement {
        StateMachine(StateMachine),
    }
    
    fn main() {
        let message = r#"
    scjson:
      - StateMachine:
          id: "sm_1"
      - StateMachine:
          id: "sm_2"
        "#;
    
        let result: Scjson = serde_yaml::from_str(message).unwrap();
        println!("{:?}", result)
    }