Deserializing with optional field for root json

Hi! I'm try to use serde to handle deserializing the incoming message. However, the message has multiple form with no key indication since it a json root message in array form.
Here is all the form the message can be in.

  • Form#1 : [ id, code, action, payload ] i.e. ( [ 132, "0ae", "Hello", {"status" : "great", "timestamp" : "xxx"}] )
  • Form#2 : [ id, code, payload ] i.e. ( [ 132, "0ae", {"status" : "great", "timestamp" : "xxx"}] )
  • Form#3 : [ id, code, error_code, error_payload ] i.e. ( [124, "a45", "ec#01", "some sample err"] )

My Objective

  • write a single serde from_str that can convert all message form.

What I have tried

  • I have try with using enum and 'serde(untagged)' but it seem only working with json with key not json root array.
  • Try it with if clause at the beginning to check if the string contain some keyword. But, there is no pattern at all.
  • Try using Option on the struct.

The code below is the minimal code that should reproduce what I try to explain

use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Serialize, Deserialize)]
pub enum Action {
    Hello,
    Bye,
    Touch,
    Kick,
}

#[derive(Serialize, Deserialize)]
struct abctest {
    msg_id: u8,
    msg_code: String,
    msg_action: Option<Action>,
    msg_payload: Value,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum ReceiveMsg {
    Type1 {
        msg_id: u8,
        msg_code: String,
        msg_action: Action,
        msg_payload: Value,
    },
    Type2 {
        msg_id: u8,
        msg_code: String,
        msg_payload: Value,
    },
    Type3 {
        msg_id: u8,
        msg_code: String,
        error_code: String,
        error_msg: String,
    },
}

fn main() {
    let input_msg = r#"[159,"01a",{"status":"Accepted","currentTime":"2023-07-14T07:33:58.528Z","interval":14400}]"#;
    let input_msg2 = r#"[129,"02a", "Touch", {"status":"Accepted","currentTime":"2023-07-14T07:33:58.528Z","interval":14400}]"#;
    let input_msg3 = r#"[94, "02a", "error-code1", "Error because blabla"]"#; // If possible

    match serde_json::from_str::<abctest>(&input_msg) {
        Ok(_) => println!("Success"),
        Err(e) => {
            println!("Failed with {}", e);
        }
    }
    match serde_json::from_str::<ReceiveMsg>(&input_msg2) {
        Ok(_) => println!("Success"),
        Err(e) => {
            println!("Failed with {}", e);
        }
    }

    match serde_json::from_str::<ReceiveMsg>(&input_msg3) {
        Ok(_) => println!("Success"),
        Err(e) => {
            println!("Failed with {}", e);
        }
    }
}

Here is the result from the run.

    Finished dev [unoptimized + debuginfo] target(s) in 0.40s
     Running `target/debug/playground`
Failed with unknown variant `status`, expected one of `Hello`, `Bye`, `Touch`, `Kick` at line 1 column 20
Failed with data did not match any variant of untagged enum ReceiveMsg
Failed with data did not match any variant of untagged enum ReceiveMsg

Are there anyway to archive my objective? Thanks. And really sorry for any confusion, English is my second language.

You need tuple variants. Named fields don't deserialize from arrays. Demo.

3 Likes

Oh my gawd. Thank you very much for your help. :pray:.