Serde: Deserialize into list of strings or list of structs

Created on 25 Apr 2017  路  3Comments  路  Source: serde-rs/serde

I am currently trying to write a visitor to be able to deserialize both a list of strings or a list of structs into a list of structs. This is the same idea as in the string-or-struct example, but wrapped in a Vec.

See this gist for a minimal example and what I have tried so far.

Specifically: I want to be able to deserialize both of the following toml-fragments into a list of structs:

children = [
    { a = "a1", b = "b1" },
    { a = "a2", b = "b2" },
]
# or 
children = [
    "a1 b1",
    "a2 b2",
]

Into:

Parent {
    children: [
        Child {
            a: "a1",
            b: "b1"
        },
        Child {
            a: "a2",
            b: "b2"
        }
    ]
}

My assumption is that I have to visit every single element within visit_seq, but I am not sure how to do this, since the elements in the list could either be strings or structs/maps.


I hope this is the right place to ask, I couldn't stick around IRC long enough to get some input over there.

support

Most helpful comment

You almost had it! The only thing going wrong was that the return type of visit_seq is Vec<Child> so both of the following are going to be using the Deserialize impl for Child, which does not have the string-or-struct behavior of your visitor.

// The return type is Vec<Child> so this is equivalent to Vec::<Child>::deserialize,
// which uses the derived Deserialize impl for Child.
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))

// ---

let mut v: Vec<T> = Vec::new();
while let Some(element) = visitor.next_element()? {
    // Inserting element into v implies that its type is Child, so the next_element
    // call will be using the derived Deserialize impl for child.
    v.push(element);
}
Ok(v)

All I changed is I split your Visitor into two Visitors. You know we need two because they have different return types. One of them produces instances of Child and the other produces Vec\.

fn seq_string_or_struct<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
    where T: Deserialize<'de> + FromStr<Err = String>,
          D: Deserializer<'de>
{
    struct StringOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for StringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        type Value = T;

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

        fn visit_str<E>(self, value: &str) -> Result<T, E>
            where E: de::Error
        {
            FromStr::from_str(value).map_err(de::Error::custom)
        }

        fn visit_map<M>(self, map: M) -> Result<T, M::Error>
            where M: de::MapAccess<'de>
        {
            Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
        }
    }

    // This is a common trick that enables passing a Visitor to the
    // `seq.next_element` call below.
    impl<'de, T> DeserializeSeed<'de> for StringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        type Value = T;

        fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
            where D: Deserializer<'de>
        {
            deserializer.deserialize_any(self)
        }
    }

    struct SeqStringOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for SeqStringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        type Value = Vec<T>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("sequence of strings or maps")
        }

        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
            where S: de::SeqAccess<'de>
        {
            let mut vec = Vec::new();
            // Tell it which Visitor to use by passing one in.
            while let Some(element) = seq.next_element_seed(StringOrStruct(PhantomData))? {
                vec.push(element);
            }
            Ok(vec)
        }
    }

    deserializer.deserialize_seq(SeqStringOrStruct(PhantomData))
}

All 3 comments

You almost had it! The only thing going wrong was that the return type of visit_seq is Vec<Child> so both of the following are going to be using the Deserialize impl for Child, which does not have the string-or-struct behavior of your visitor.

// The return type is Vec<Child> so this is equivalent to Vec::<Child>::deserialize,
// which uses the derived Deserialize impl for Child.
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))

// ---

let mut v: Vec<T> = Vec::new();
while let Some(element) = visitor.next_element()? {
    // Inserting element into v implies that its type is Child, so the next_element
    // call will be using the derived Deserialize impl for child.
    v.push(element);
}
Ok(v)

All I changed is I split your Visitor into two Visitors. You know we need two because they have different return types. One of them produces instances of Child and the other produces Vec\.

fn seq_string_or_struct<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
    where T: Deserialize<'de> + FromStr<Err = String>,
          D: Deserializer<'de>
{
    struct StringOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for StringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        type Value = T;

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

        fn visit_str<E>(self, value: &str) -> Result<T, E>
            where E: de::Error
        {
            FromStr::from_str(value).map_err(de::Error::custom)
        }

        fn visit_map<M>(self, map: M) -> Result<T, M::Error>
            where M: de::MapAccess<'de>
        {
            Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
        }
    }

    // This is a common trick that enables passing a Visitor to the
    // `seq.next_element` call below.
    impl<'de, T> DeserializeSeed<'de> for StringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        type Value = T;

        fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
            where D: Deserializer<'de>
        {
            deserializer.deserialize_any(self)
        }
    }

    struct SeqStringOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for SeqStringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        type Value = Vec<T>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("sequence of strings or maps")
        }

        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
            where S: de::SeqAccess<'de>
        {
            let mut vec = Vec::new();
            // Tell it which Visitor to use by passing one in.
            while let Some(element) = seq.next_element_seed(StringOrStruct(PhantomData))? {
                vec.push(element);
            }
            Ok(vec)
        }
    }

    deserializer.deserialize_seq(SeqStringOrStruct(PhantomData))
}

I filed #902 to cut down some of the boilerplate here. Really it should be possible to implement this as:

fn seq_string_or_struct<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
    where T: Deserialize<'de> + FromStr<Err = String>,
          D: Deserializer<'de>
{
    struct StringOrStruct<T>(PhantomData<T>);

    impl<'de, T> de::Visitor<'de> for StringOrStruct<T>
        where T: Deserialize<'de> + FromStr<Err = String>
    {
        /* ... */
    }

    seed::Vec(seed::Any(StringOrStruct(PhantomData))).deserialize(deserializer)
}

Thank you very much for your quick response, I tested your suggested solution with success. It took me a bit to get my head around what is happening, but I think I am getting there.

Thanks again for the great support and for pushing this amazing library ever further! This issue can be closed from my side, you probably have everything you want to track in #902?

Was this page helpful?
0 / 5 - 0 ratings