There seems to be a regression in serde 1.0 compared to serde 0.9 in regards to how deserialize of JSON numbers defined as strings into integer types is implemented.
For example, this code works fine on serde 0.9 but fails in serde 1.0:
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[derive(Deserialize, Eq, PartialEq, Debug)]
struct Test {
test: u16
}
fn main() {
let input = r#"{"test":"100"}"#;
let t: Test = serde_json::from_str(input).unwrap();
assert_eq!(t, Test{test: 100});
}
In serde 1.0 I get:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ErrorImpl { code: Message("invalid type: string \"100\", expected u16"), line: 1, column: 13 }', /checkout/src/libcore/result.rs:859
Yes these implicit conversions were removed in Serde 1.0. See https://github.com/serde-rs/serde/pull/839 for some justification.
The conversion can be done explicitly where appropriate.
use std::fmt::Display;
use std::str::FromStr;
use serde::de::{self, Deserialize, Deserializer};
#[derive(Deserialize, Eq, PartialEq, Debug)]
struct Test {
#[serde(deserialize_with = "from_str")]
test: u16
}
fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where T: FromStr,
T::Err: Display,
D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
T::from_str(&s).map_err(de::Error::custom)
}
Thanks for the suggestion. It works fine for my original example but I cannot get it to work for optional fields.
I have something like this:
#[derive(Deserialize)]
struct A {
#[serde(deserialize_wtih="deser_b")]
a: String,
b: Option<B>,
}
#[derive(Deserialize)]
struct B {
#[serde(deserialize_with="from_str_optional")]
b: Option<u16>
}
fn deser_b<'de, D>(de: D) -> Result<Option<B>, D::Error>
where D: serde::Deserializer<'de>
{
let deser_res: json::Value = try!(serde::Deserialize::deserialize(de));
match deser_res {
json::Value::Object(ref obj) if obj.len() == 0 => Ok(None),
obj @ json::Value::Object(_) => {
Ok(try!(json::from_value(obj).map_err(|e| {
serde::de::Error::custom(format!("could not deser b: {}", e))
})))
}
_ => Ok(None),
}
}
fn from_str_optional<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where T: FromStr,
T::Err: Display,
D: serde::Deserializer<'de>
{
let deser_res: Result<json::Value, _> = serde::Deserialize::deserialize(deserializer);
match deser_res {
Ok(json::Value::String(s)) => T::from_str(&s).map_err(serde::de::Error::custom).map(Option::from),
Ok(v) => {
warn!("string expected but found something else: {}", v);
Ok(None)
},
Err(_) => Ok(None)
}
}
deser_b fails for an input like {"a": "test"} because it cannot find a field b so it seems from_str_optional is not used.
Any clues what am I doing wrong?
Thanks
Yep, the deserialize_with controls how the field is deserialized if a value is present but that is independent of what happens when the value is not present. If you want a missing value to be treated as None, use:
#[serde(default, deserialize_with = "deser_b")]
Thanks for your help! It works now. I will close the ticket.
I'm trying to adapt this workaround for a situation where a field might be an actual int, or might be a stringified version of an int, but I can't figure out a friendly way to cater for both situations (See reference above)
For anyone coming from a search engine, you can use the serde_with crate.
@ibraheemdev Can you provide an example of that ???
@dtolnay Is there a more straightforward cleaner way of doing this now ??
Most helpful comment
Yes these implicit conversions were removed in Serde 1.0. See https://github.com/serde-rs/serde/pull/839 for some justification.
The conversion can be done explicitly where appropriate.