Is there is a simpler way to convert a type upon deserialization?

随声附和 提交于 2019-12-01 02:17:03

问题


Using serde_json, I have JSON objects with Strings that I need to convert to floats. I've stumbled upon a custom deserializer solution, but it seems like a hack. Here is a working playground example of the code below.

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use serde_json::Error;
use serde::de::{Deserialize, DeserializeOwned, Deserializer};

#[derive(Serialize, Deserialize)]
struct Example {
    #[serde(deserialize_with = "coercible")]
    first: f64,
    second: f64,
}

fn coercible<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: DeserializeOwned,
    D: Deserializer<'de>,
{
    use serde::de::Error;
    let j = String::deserialize(deserializer)?;
    serde_json::from_str(&j).map_err(Error::custom)
}

fn typed_example() -> Result<(), Error> {
    let data = r#"["3.141",1.618]"#;
    let e: Example = serde_json::from_str(data)?;
    println!("{} {}", e.first * 2.0, e.second * 2.0);
    Ok(())
}

fn main() {
    typed_example().unwrap();
}

The above code compiles and runs as you would expect, outputting two floats.

I'm trying to learn how the deserializer solution works, but I'd like to know if I'm headed in the right direction or if there is a better way to do this.


回答1:


Using coercible worked kind-of by accident. With it, the input "3.141" was stripped of its ""s, so I had 3.141 being fed into serde_json::from_str(&j), which appropriately returned a float. This accidental solution broke easily and confusingly when, e.g., the input JSON contained unexpected values.

I read the Serde docs (a great learning exercise) and came up with the appropriate way to convert a string to a f64 upon deserialization of JSON (working playground here):

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::fmt;
use serde_json::Error;
use serde::de::{self, Deserializer, Unexpected, Visitor};

#[derive(Serialize, Deserialize)]
struct Example {
    #[serde(deserialize_with = "string_as_f64")]
    first: f64,
    second: f64,
}

fn string_as_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_f64(F64Visitor)
}

struct F64Visitor;
impl<'de> Visitor<'de> for F64Visitor {
    type Value = f64;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a string representation of a f64")
    }
    fn visit_str<E>(self, value: &str) -> Result<f64, E>
    where
        E: de::Error,
    {
        value.parse::<f64>().map_err(|_err| {
            E::invalid_value(Unexpected::Str(value), &"a string representation of a f64")
        })
    }
}

fn typed_example() -> Result<(), Error> {
    let data = r#"["3.141",1.618]"#;
    let e: Example = serde_json::from_str(data)?;
    println!("{} {}", e.first * 2.0, e.second * 2.0);
    Ok(())
}

fn main() {
    typed_example().unwrap();
}

Kudos to the Serde devs, because although the Serde documentation seemed totally obtuse to my eyes, it actually proved to be very helpful and comprehensible. I just had to start from the top and read through slowly.



来源:https://stackoverflow.com/questions/44836327/is-there-is-a-simpler-way-to-convert-a-type-upon-deserialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!