Possible serde_json roundtrip bug
I'm fairly certain this is a bug, but I'm unsure whether it's in the serde_json or rust_decimal crates.
Specific JSON values will not round-trip when deserializing using serde_json::from_value.
Setup
rust_decimal = { version = "1.39.0", features = ["serde-with-arbitrary-precision"] }
serde_json = { version = "1.0.133", features = ["arbitrary_precision", "preserve_order"] }
Tests
#[test]
fn test_reading_decimal() {
#[derive(Serialize, Deserialize)]
struct Foo {
#[serde(serialize_with = "rust_decimal::serde::arbitrary_precision::serialize")]
zero: Decimal,
}
let input = r#"{"zero":0.0}"#;
let foo: Foo = serde_json::from_str(input).unwrap();
assert_eq!(foo.zero.to_string(), "0.0");
}
#[test]
fn test_writing_decimal() {
#[derive(Serialize, Deserialize)]
struct Foo {
#[serde(serialize_with = "rust_decimal::serde::arbitrary_precision::serialize")]
zero: Decimal,
}
let input = r#"{"zero":0.0}"#;
let foo: Foo = serde_json::from_str(input).unwrap();
let output = serde_json::to_string(&foo).unwrap();
assert_eq!(output, input);
}
/// This test fails because 0.0 gets converted into 0 when converting to `Foo` from a
/// `serde_json::Value`. Deserialization of `Foo` works differently depending on if it is
/// being deserialized from a string or `serde_json::Value`.
#[test]
fn test_reading_from_value() {
#[derive(Serialize, Deserialize)]
struct Foo {
#[serde(serialize_with = "rust_decimal::serde::arbitrary_precision::serialize")]
zero: Decimal,
}
let input = r#"{"zero":0.0}"#;
let foo: Value = serde_json::from_str(input).unwrap();
let output: Foo = serde_json::from_value(foo).unwrap();
let output_str = serde_json::to_string(&output).unwrap();
assert_eq!(output_str, input);
}
Details
The reason serde_json::Value deserialization works differently is due to this block of code.
https://github.com/serde-rs/json/blob/master/src/number.rs#L558-L562
Even with the arbitrary_precision feature, if serde_json thinks the f64 value can be serialized to the same String value that is contained in the arbitrary precision number, then it will call visit_f64 instead of visit_map which is the path that is taken when parsing from a &str instead of a serde_json::Value.
This looks like an issue in serde_json::Value's deserialization here. To send arbitrary precision numbers it would have to be sent as the special arbitrary precision map.
However, I imagine that could break a lot of code, so I'm not sure that there will be much appetite for changing this. My best suggestion here is to not go via serde_json::Value. Both as a general rule and in this specific case, it can hide specifics about the type which leads to suboptimal deserialization. It's very well suited to holding unknown types, but it is not a good intermediate representation.