Serde from skyscanner

Greetings all,
I am trying to Serialize JSON returned from Skyscanner API into a Rust series of structs/enums/etc.

Below is the JSON string fragment that is giving me the grief.

{
  "sessionToken": "==-cells4",
  "status": "RESULT_STATUS_COMPLETE",
  "action": "RESULT_ACTION_REPLACED",
  "content": {
    "results": {
      "itineraries": {
        "some_random_name_here_1" : { field, array, field, etc}, 
        "some_new_random_name_2": { same structure as above struct },
        "some_new_random_name_3": { same structure as above },
        "some_new_random_name_4": { same structure as above },
        etc
        
}

As you see there is a Sequence of structures there with "random_name" as field.
But in fact it is just an array of structs, not a sequence and the "random name" acts like a tag and struct itself contains data.

I think (with my limited serde knowledge) it may be best to try to convert the sequence of JSON structues into a Rust HashMap where the "random_name" is the Key and the struct itself is the Value.

Or perhaps to make the entire sequence into Rust Vector of structs and the "random_name" can be called some "fixed name".

Can anyone please suggest the easiest way to deal with this kind of JSON text to convert it to sane-looking Rust series of data types? A concrete Rust example or a pointer to something similar perhaps?

Many thanks.

Yeah, if you have arbitrary dyamic keys, you'll pretty much have to use a map data structure. I don't think there's an "easier" way, although it's not exactly hard to do. I mean, just put a HashMap<String, _> where you would put a Vec<_>?

2 Likes

thanks, this is very similar (or same) as what I have just tried and by some sort of Rust-Serde-Magic, it appears to work.

Am I hallucinating or is Rust reading my mind?

Note that I used #[serde(flatten)], if I remove flatten then it does not work because it is looking for a field named i.

I dont know serde well enough yet but it appears to be simply magic piece of software!

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Itineraries {
    #[serde(flatten)]
    i: HashMap<String, Itinerary>,
}

truly, most of the time, it just works like magic. for your specific case, you can also use custom deserialization function to skip the intermediate HashMap if you don't care about those "unique" keys and only need a Vec of values.

// the inner type can just be derived
#[derive(Deserialize)]
struct Itinerary {
    //...
}

// the container type is manually implemented
struct Itineraries (Vec<Itinerary>);

impl<'de> Deserialize<'de> for Itineraries {
    fn deserialize<D>(d: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        d.deserialize_map(Visitor)
    }
}

struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = Itineraries;
    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(formatter, "an map with random keys")
    }
    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        let mut items = Vec::with_capacity(map.size_hint().unwrap_or_default());
        // ignore keys and only push values
        // it is required to call `next_key()` before `next_value`,
        // even if the key is not needed.
        while let Some(_key) = map.next_key::<&'de str>()? {
            items.push(map.next_value()?);
        }
        Ok(Itineraries(items))
    }
}
1 Like

I'm honestly not sure what you are referring to here.

The structure of the acceptible JSON is generated from the structure of your UDTs, which is known at compile time, and parsed by the Serialize/Deserialize derive macros. The structure of your types must exactly mirror the structure of the JSON, otherwise you will get a deserialization error. Various attributes such as #[flatten] can modify this behavior in commonly-used ways, by telling the derive macros to adjust the generated code. (The flatten wouldn't even have been required had you used a single-field "newtype" tuple struct instead.)

There is no mind reading going on. Everything that Serde is doing is directly inferred from the source code.

1 Like

thanks!
yes, I suspected as much, I should have put a smily face on my comment.

I was trying to express how much I like Rust and serde and the whole ecosystem.
It is simply superb software.
And the Rust community is fantastic, including right here on this very forum.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.