serde-rs / serde

Serialization framework for Rust
https://serde.rs/
Apache License 2.0
9.16k stars 774 forks source link

Treat string and list of strings both as a vec #889

Closed dtolnay closed 7 years ago

dtolnay commented 7 years ago

From IRC:

Guys, with serde. Say I have an object that could be either a String, or a list of strings. How do I tell Serde to always create a vec?

dtolnay commented 7 years ago
#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::fmt;

use serde::de::{self, value, Deserialize, Deserializer, Visitor, SeqAccess};

#[derive(Deserialize, Debug)]
struct Cetra {
    #[serde(deserialize_with = "string_or_vec")]
    v: Vec<String>,
}

fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
    where D: Deserializer<'de>
{
    struct StringOrVec;

    impl<'de> Visitor<'de> for StringOrVec {
        type Value = Vec<String>;

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

        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
            where E: de::Error
        {
            Ok(vec![s.to_owned()])
        }

        fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
            where S: SeqAccess<'de>
        {
            Deserialize::deserialize(value::SeqAccessDeserializer::new(seq))
        }
    }

    deserializer.deserialize_any(StringOrVec)
}

fn main() {
    let j = "{\"v\":\"s\"}";
    println!("{:?}", serde_json::from_str::<Cetra>(j).unwrap());

    let j = "{\"v\":[\"a\",\"b\"]}";
    println!("{:?}", serde_json::from_str::<Cetra>(j).unwrap());
}
oovm commented 3 years ago

I want to ask if it is possible to implement a generic version of type_or_vec?

JackYoustra commented 3 years ago

@dtolnay Do you know any way to generically do this? I tried by doing a variant of what you described, but instead of calling visit_seq on a seq type and calling visit_map on a map type, it just calls visit_seq on both a seq and a map type (unreachable! never gets hit).

fn string_or_seq_string<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
    where D: Deserializer<'de>, T: Deserialize<'de> + std::fmt::Debug
{
    log!("in the deserialization function");
    struct StringOrVec<T>(PhantomData<Vec<T>>);

    impl<'de, T: Deserialize<'de> + std::fmt::Debug> de::Visitor<'de> for StringOrVec<T> {
        type Value = Vec<T>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_fmt(format_args!("{} or list thereof", std::any::type_name::<T>()))
        }

        fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
            where S: de::SeqAccess<'de>
        {
            log!("Seq type {:?}", std::any::type_name::<S>());
            let retval = Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor));
            log!("Internal result {:?}", retval);
            retval
        }

        fn visit_map<S>(self, visitor: S) -> Result<Self::Value, S::Error>
            where S: de::MapAccess<'de>,
        {
            unreachable!();
        }
    }

    let retval = deserializer .deserialize_any(StringOrVec(PhantomData));
    log!("Result is {:?}", retval);
    log!("out of the deserialization function");
    retval
}