问题
Using serde_json, I have JSON objects with String
s 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