serde icon indicating copy to clipboard operation
serde copied to clipboard

Lack of deserialisation context

Open mozalmic opened this issue 2 years ago • 4 comments

I have to transform deserialised values before putting them out based on external map. I got stuck on how to implement this without some kind of context I would use in Deserialise:

pub struct SomeData {
    some_field: u32,
}

pub struct Context {
    map: HashMap<&'static str, u32>,
}

impl<'de> Deserialize<'de> for SomeData {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct SomeDataVisitor {
            context: Context,
        };
        impl<'de> Visitor<'de> for SomeDataVisitor {
            type Value = SomeData;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("SomeData format")
            }

            fn visit_str<E>(self, value: &str) -> Result<SomeData, E>
            where
                E: de::Error,
            {
                let val = *self.context.map.get(value).unwrap_or(&0);
                Ok(SomeData { some_field: val })
            }
        }

        deserializer.deserialize_identifier(SomeDataVisitor { context: context })
    }
}

Any ideas how to pass Context into the Deserialiser / Visitor are appreciated.

mozalmic avatar Apr 29 '22 07:04 mozalmic

You can implement DeserializeSeed for your Context -- this will allow you to deserialize top-level SomeData struct, but unfortunately, this approach is not composable

Mingun avatar Apr 29 '22 11:04 Mingun

Yep, SomeData is a part of a large model. In real world it could be struct Country which has been previously serialised to the yaml as shortcuts:

counterparties:
  - name: SomeCompany
    location: GB # here is the reference to Country dictionary
    contacts:
      - name: John
        address: 
          country: GB # and here too
          city: London
      - name: Bob
        address: ...

GB is shortcut for Country I wanna substitute from external dictionary (map).

As far as I understood DeserializeSeed approach requires passing context manually by invoking seq.next_element_seed(context) each time it faces Country property. But it could not be done if object tree is complex, right?

mozalmic avatar Apr 29 '22 13:04 mozalmic

But it could not be done if object tree is complex, right?

Yes, at least with serde's derives. Probably the more realistic approach is to deserialize into intermediate stateless structure and then convert it to the final one

Mingun avatar Apr 29 '22 15:04 Mingun

I've tried different approaches and end up with the conclusion that it would be great to introduce new #derive DeserializeSeed for such cases. What do you think about this?

mozalmic avatar May 11 '22 16:05 mozalmic

Hi @mozalmic, hopefully you got this working. If there is still an issue, I would recommend following up in any of the resources shown in https://www.rust-lang.org/community. This library is one of the most widely used Rust libraries and plenty of people will be able to provide guidance about it.

dtolnay avatar Jul 09 '23 07:07 dtolnay