Search code examples
rustdeserializationserde

Serde deserialising tuple containing varying types


I have some JSON of this format:

[
  123,
  {“some struct”: “is always here”},
  {“optional struct”: ”is not always here”},
  “String1”,
  “String2”
]

Note that the second struct is sometimes not present - the array will sometimes contain 4 elements, other times 5. This means that seq[1] is always the struct, but seq[2] may either be the struct or String1.

I’ve written a custom Deserialize using the SeqAccess visitor, looking something like this:

let number: usize = v.next_element()?;
let struct: SomeStruct = v.next_element()?;
let maybe_struct: OtherStruct = v.next_element()?;
// …

The problem is that calling v.next_element() where the struct is NOT present, it wants to deserialise immediately into OtherStruct, which fails because the next element is String1. If I call v.next_element() again, the visitor has already moved onto the next element, and I’ll get String2.

I think I’m looking at this wrong and probably need to write a visitor which receives a method call for each element in the sequence, I’ve not been able to locate any examples or documentation that explains how to do this.


Solution

  • You should be able to do this without using a custom deserializer if you wrap the combinations in an enum and use serde's untagged attribute.

    Something like this should work

    use serde::Deserialize;
    use serde_json::from_str;
    
    #[derive(Debug, Deserialize)]
    #[serde(untagged)]
    enum Either {
        Four(i32, SomeStruct, String, String),
        Five(i32, SomeStruct, OtherStruct, String, String),
    }
    
    pub fn main() {
        let src_with_four = todo!();
        let src_with_five = todo!();
        let f1 = from_str::<Either>(src_with_four);
        let f2 = from_str::<Either>(src_with_five);
        println!("{f1:?}");
        println!("{f2:?}");
    }
    

    Updated to use tuple variants directly based on @cafce23's comment

    Previous struct version in playground

    @cafce25's example in the playground