paupino / rust-decimal

Decimal number implementation written in pure Rust suitable for financial and fixed-precision calculations.
https://docs.rs/rust_decimal/
MIT License
1.03k stars 183 forks source link

Can't deserialize with `float_option` when null in an internally tagged enum #596

Closed mtkennerly closed 1 year ago

mtkennerly commented 1 year ago

Hi! I just ran into this issue where null fails to parse when it's inside of an internally tagged enum.

Cargo.toml:

[package]
name = "decimal-test"
version = "0.1.0"
edition = "2021"

[dependencies]
rust_decimal = { version = "1.30.0", features = ["serde-with-float"] }
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.99"

main.rs:

use rust_decimal::Decimal;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
enum Tagged {
    Example {
        #[serde(default, with = "rust_decimal::serde::float_option")]
        value: Option<Decimal>,
    }
}

fn main() {
    let tests = vec![
        r#"{"type": "Example", "value": 1.2}"#,
        r#"{"type": "Example"}"#,
        r#"{"type": "Example", "value": null}"#,
    ];

    for test in tests {
        println!("Test: {test}");
        let parsed = serde_json::from_str::<Tagged>(test).unwrap();
        dbg!(parsed);
        println!();
    }
}

Output:

Test: {"type": "Example", "value": 1.2}
[src\main.rs:23] parsed = Example {
    value: Some(
        1.2,
    ),
}

Test: {"type": "Example"}
[src\main.rs:23] parsed = Example {
    value: None,
}

Test: {"type": "Example", "value": null}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("invalid type: null, expected a Decimal type representing a fixed-point number", line: 0, column: 0)', src\main.rs:22:59

It works fine with a struct instead:

#[derive(Debug, Deserialize)]
struct Tagged {
    #[serde(default, with = "rust_decimal::serde::float_option")]
    value: Option<Decimal>,
}
mtkennerly commented 1 year ago

I realized that it also works if I just remove the #[serde(default, with = "rust_decimal::serde::float_option")] line altogether. Maybe this is just an issue with the Serde attributes disabling some internal behavior when they're present.

eigenein commented 9 months ago

I'd like to reopen the issue, it seems to relate to str_option too

Indeed, removing the attribute helps, but it becomes an issue again when one needs to use multiple Serde features and different #[serde(with = ...)] options (e.g. serde-with-str for one struct and serde-float for another)

Tony-Samuels commented 9 months ago

This is a really annoying limitation of serde that's bitten me multiple times. When using certain tagging representations or using flatten, serde loses the information on what types it's working with.

Serde has refused to implement any proposed solutions, so for now I'm afraid this will just have to be worked around by changing you serialisation format.

I don't expect this will be fixed until a mythical serde 2.0 comes out.