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

Round-trip failure: ser -> de transforms Float(1.0) into Integer(1) #337

Closed
saethlin opened this issue Nov 16, 2021 · 1 comment · Fixed by #363
Closed

Round-trip failure: ser -> de transforms Float(1.0) into Integer(1) #337

saethlin opened this issue Nov 16, 2021 · 1 comment · Fixed by #363

Comments

@saethlin
Copy link

This example program probably ought to exit successfully

fn main() {
    let input = "1.0";
    let v: ron::Value = ron::from_str(input).unwrap();
    println!("{:?}", v);
    let ser = ron::to_string(&v).unwrap();
    println!("{:?}", ser);
    let roundtrip = ron::from_str(&ser).unwrap();
    println!("{:?}", roundtrip);
    assert_eq!(v, roundtrip);
}

But it doesn't:

Number(Float(Float(1.0)))
"1"
Number(Integer(1))
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `Number(Float(Float(1.0)))`,
 right: `Number(Integer(1))`', src/main.rs:9:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This seems plausible as a ser/de behavior if RON only has a number type, but if that's the case ron::Value::Number should depend only on the numeric value not the discriminant.

@juntyr
Copy link
Member

juntyr commented Dec 10, 2021

I'm not quite sure how this can be fixed in a nice way. If you just have a specific usecase where the roundtrip should work, I'd suggest using

ron::ser::to_string_pretty(&v, ron::ser::PrettyConfig::new().decimal_floats(true))

Even though the deserialized Value is an Integer in this case, it still deserializes perfectly into an f64 (handled internally by serde's Deserialize impl for f64).

The only thing that is really missing is describe how two structurally different Numbers can deserialize into the same value. This could be done, e.g. like so:

impl PartialEq for Number {
    fn eq(&self, other: &Self) -> bool {
        use serde::de::IntoDeserializer;

        match (self, other) {
            (Self::Integer(a), Self::Integer(b)) => a.eq(b),
            (Self::Integer(a), Self::Float(b)) => {
                let af: Result<f64, serde::de::value::Error> = f64::deserialize(a.into_deserializer());

                af.map_or(false, |a| Float(a).eq(b))
            },
            (Self::Float(a), Self::Integer(b)) => {
                let bf: Result<f64, serde::de::value::Error> = f64::deserialize(b.into_deserializer());

                bf.map_or(false, |b| Float(b).eq(a))
            },
            (Self::Float(a), Self::Float(b)) => a.eq(b),
        }
    }
}

However, this would then also require adjusting the Hash, PartialOrd and Ord impls and get quite confusing.

Another quick and dirty fix could be to simply enable decimal_floats by default (both in pretty and non-pretty mode).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants