-1

I have three structs A, B and C that are being fed to a method process_data() as a list of JSON. All the three structs are serde serializable/deserializable. They are defined below as follows:-

#[derive(Serialize, Deserialize)]
struct A {
  pub a: u32,
}

#[derive(Serialize, Deserialize)]
struct B {
  pub b: u32,
}

#[derive(Serialize, Deserialize)]
struct C {
  pub c: u32,
}

The function signature looks like this

fn process_data(data: String) {}

data can have any of these structs but its guaranteed that one of A, B or C will be there

data = "A{a: 1}" 
or data = "[A{a:1}, B{b:1}, C{c:1}]"
or data = "[B{b:1}, A{a:1}, C{c:1}]"

I am looking for a way to process the variable data through serde within process_data, such that I can extract the structs from the data stream.

What I have tried so far. I tried defining a struct called Collect which holds all the structs like this:-

#[derive(Serialize, Deserialize)]
struct Collect {
 pub a1: Option<A>
 pub b1: Option<B>,
 pub c1: Option<C>
}

and then process the data as follows:-

serde_json::from_str::<Collect>(data.as_str()) 

But the previous command throws an error. Also I am looking to preserve the order of the vector in which the data is coming

I am not sure if serde will work in this case.

blueStack453
  • 59
  • 1
  • 5

1 Answers1

1

I'll assume you wanted the following JSON data:

[{"a":1}, {"b":1}, {"c":1}]

So, you want to deserialize to Vec<Collect>:

  • {"a":1} only contains the subfield from struct A, no additional wrap for a1. Normally you handle missing levels by tagging with #[serde(flatten)]
  • {"a":1} doesn't contain the subfields from b1 or c1. Normally you handle missing fields by tagging (an Option) with #[serde(default)]

It seems that the combination of the two doesn't work on deserialization.

Instead, you can deserialize to an untagged enum:

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum CollectSer {
    A { a: u32 },
    B { b: u32 },
    C { c: u32 },
}

If you do absolutely want to use your Collect as is, with the Options, you can do that still:

#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(from = "CollectSer")]
struct Collect {
    #[serde(flatten)]
    pub a1: Option<A>,
    #[serde(flatten)]
    pub b1: Option<B>,
    #[serde(flatten)]
    pub c1: Option<C>,
}

impl From<CollectSer> for Collect {
    fn from(cs: CollectSer) -> Self {
        match cs {
            CollectSer::A { a } => Collect {
                a1: Some(A { a }),
                ..Default::default()
            },
            CollectSer::B { b } => Collect {
                b1: Some(B { b }),
                ..Default::default()
            },
            CollectSer::C { c } => Collect {
                c1: Some(C { c }),
                ..Default::default()
            },
        }
    }
}

I suggest you just stick with the enum though, it's a lot more rustic.

Playground

(Apologies if I misguessed the structure of your data, but if so, I suppose you can at least point out the difference with this?)

Caesar
  • 6,733
  • 4
  • 38
  • 44
  • Please do still fix the JSON in your question. Maybe somebody else has a brighter idea based on it. – Caesar Feb 14 '23 at 07:48
  • I figured that I still might need to process the input from "[A{a:1}, B{b:1}, C{c:1}]".Like in the future I have more structs that have similar attributes like D{a: 1} then from this answer, I wont be able to identify, which struct does the attribute belong to. – blueStack453 Feb 14 '23 at 08:39
  • Then you should remove the reference to JSON because that example isn't JSON – cafce25 Feb 14 '23 at 10:03
  • What data format is that even supposed to be? [RON](https://docs.rs/ron/latest/ron/)? That seems to use `()` not `{}`. Or did you maybe want `["A":{"a":1}, "B":{"b":1}, "C":{"c":1}]` – Caesar Feb 14 '23 at 12:11