Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indicate how to call custom deserializers from custom deserializers #1403

Closed
bbangert opened this issue Oct 3, 2018 · 1 comment
Closed
Labels

Comments

@bbangert
Copy link

bbangert commented Oct 3, 2018

Writing a custom deserialize function and using it with the field attribute is relatively easy. However, it's not clear how to chain these together. That is, to use a custom deserializer from a custom deserializer.

An example in this case would be to utilize a deserializer like the comma separated one here (#581 (comment)), but use it in a separate custom deserialize that allows the field to be Option<Vec>.

@dtolnay
Copy link
Member

dtolnay commented Oct 4, 2018

Here's the code from #581 (comment) but with the comma-separated field optional.

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::fmt::{self, Display};
use std::iter::FromIterator;
use std::marker::PhantomData as Phantom;
use std::str::FromStr;

use serde::de::{self, Deserializer, Visitor};

#[derive(Deserialize, Debug)]
struct S {
    #[serde(default, deserialize_with = "optional_comma_separated")]
    f: Option<Vec<i32>>,
}

fn optional_comma_separated<'de, V, T, D>(deserializer: D) -> Result<Option<V>, D::Error>
where
    V: FromIterator<T>,
    T: FromStr,
    T::Err: Display,
    D: Deserializer<'de>,
{
    struct OptionalCommaSeparated<V, T>(Phantom<V>, Phantom<T>);

    impl<'de, V, T> Visitor<'de> for OptionalCommaSeparated<V, T>
    where
        V: FromIterator<T>,
        T: FromStr,
        T::Err: Display,
    {
        type Value = Option<V>;

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

        fn visit_none<E>(self) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(None)
        }
    
        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
        where
            D: Deserializer<'de>,
        {
            comma_separated(deserializer).map(Some)
        }
    }

    let visitor = OptionalCommaSeparated(Phantom, Phantom);
    deserializer.deserialize_option(visitor)
}

fn comma_separated<'de, V, T, D>(deserializer: D) -> Result<V, D::Error>
where
    V: FromIterator<T>,
    T: FromStr,
    T::Err: Display,
    D: Deserializer<'de>,
{
    struct CommaSeparated<V, T>(Phantom<V>, Phantom<T>);

    impl<'de, V, T> Visitor<'de> for CommaSeparated<V, T>
    where
        V: FromIterator<T>,
        T: FromStr,
        T::Err: Display,
    {
        type Value = V;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("string containing comma-separated elements")
        }

        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            let iter = s.split(",").map(FromStr::from_str);
            Result::from_iter(iter).map_err(de::Error::custom)
        }
    }

    let visitor = CommaSeparated(Phantom, Phantom);
    deserializer.deserialize_str(visitor)
}

fn main() -> Result<(), serde_json::Error> {
    println!("{:?}", serde_json::from_str::<S>(r#"{"f":"1,2,3"}"#)?);
    println!("{:?}", serde_json::from_str::<S>(r#"{"f":null}"#)?);
    println!("{:?}", serde_json::from_str::<S>(r#"{}"#)?);
    Ok(())
}

@dtolnay dtolnay added the support label Nov 9, 2018
@dtolnay dtolnay closed this as completed Nov 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants