-
Notifications
You must be signed in to change notification settings - Fork 185
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
serde-arbitrary-precision
without serde-float
#602
Comments
Hmm, I see what you're talking about. To describe this a bit - if we had the following dependencies: [dependencies]
serde = { version = "*", features = ["derive"] }
serde_json = "*"
rust_decimal = { version = "*", default-features = false, features = ["serde-arbitrary-precision"] }
rust_decimal_macros = "*" And this code: use rust_decimal_macros::dec;
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct RatioWithout {
value: rust_decimal::Decimal,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct RatioWith {
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
value: rust_decimal::Decimal,
}
fn main() {
let ratio_with = RatioWith {
value: dec!(3.14159),
};
let ratio_without = RatioWithout {
value: dec!(3.14159),
};
println!("with: {}", serde_json::to_string(&ratio_with).unwrap());
println!("without: {}", serde_json::to_string(&ratio_without).unwrap());
let json_with: RatioWith = serde_json::from_str(r#"{"value":3.14159}"#).unwrap();
let json_without: RatioWithout = serde_json::from_str(r#"{"value":3.14159}"#).unwrap();
println!("json_with: {:?}", json_with);
println!("json_without: {:?}", json_without);
} The output is:
The question is about serialization of the value given that we've enabled the "default" feature, and not just the "with" variant. I think this goes back to the intention of the with vs non-with pattern. The general idea for the The non-"with" pattern is intended to be a blanket controlling feature for the decimal library. i.e. you are the library publishing/creating numbers and this is how you want to do it. Alternatively, it can provide a "default" serialization/deserialization format (you can always override with Consequently, I do think this is unexpected behavior - the non-with pattern should behave the same as if So a PR would definitely be useful and much appreciated - thank you @drdo ! Regarding the promotion of The serde features in this library are a bit of a complex matrix - I think in general they probably deserve their own readme section to better describe how to use them (or how they can be used). |
Do you think it ever makes sense to enable several of Maybe even delete |
I think they should probably be mutually exclusive. I'd have to have a bit of a deeper dive regarding Either way - simplifying these feature combinations would definitely help reduce the complexity of the area! |
Just adding my two cents. I came upon this issue after looking into a strange deserialization issue in my application. When inserting the above code by @paupino as my main, leaving the rest of my app and its dependencies intact, I would run into the following error:
I was stuck on this for quite a while since I was sure my code had worked before and could not find any way to fix it. In the end, using
And the fix appears to be to enable the What am I trying to say is that while I needed to enable |
I think I originally misunderstood the context of this question! Originally, I thought I tend to agree about serializing as a string by default and instead only serializing as a float on demand. I'll look into making this change however I need to do a bit of planning about how to handle this change since it'd technically be breaking. In the meantime, as a workaround - you can do this:
rust_decimal = { version = "*", features = ["serde-with-arbitrary-precision", "serde-with-str"] } #[derive(serde::Serialize, serde::Deserialize)]
struct Total {
#[serde(
deserialize_with = "rust_decimal::serde::arbitrary_precision::deserialize",
serialize_with = "rust_decimal::serde::str::serialize"
)]
value: Decimal,
}
let total = Total { value: dec!(1.23) };
let serialized = serde_json::to_string(&total)?;
assert_eq!(r#"{"value":"1.23"}"#, serialized);
let deserialized: Total = serde_json::from_str(&serialized)?;
assert_eq!(dec!(1.23), deserialized.value);
let deserialized: Total = serde_json::from_str(r#"{"value":1.23}"#)?;
assert_eq!(dec!(1.23), deserialized.value); This forces arbitrary precision to be used for deserialization while keeping string as a serialization format. If you wanted to slightly make it less "wordy" you could also do the following to keep the "default" serialization as string but only deserialize with arbitrary precision:
rust_decimal = { version = "*", features = ["serde-with-arbitrary-precision"] } #[derive(serde::Serialize, serde::Deserialize)]
struct Total {
#[serde(
deserialize_with = "rust_decimal::serde::arbitrary_precision::deserialize",
)]
value: Decimal,
}
let total = Total { value: dec!(1.23) };
let serialized = serde_json::to_string(&total)?;
assert_eq!(r#"{"value":"1.23"}"#, serialized);
let deserialized: Total = serde_json::from_str(&serialized)?;
assert_eq!(dec!(1.23), deserialized.value);
let deserialized: Total = serde_json::from_str(r#"{"value":1.23}"#)?;
assert_eq!(dec!(1.23), deserialized.value); |
@paupino The concern I was addressing with my comment was in response to the initial question raised by this issue:
Since enabling |
Cool - I actually tend to agree with you here. For this behavior I'd expect a little bit of the reverse logic - enable float serialization on demand. e.g.
rust_decimal = { version = "*", features = ["serde-arbitrary-precision", "serde-with-float"] } #[derive(serde::Serialize, serde::Deserialize)]
struct Total {
#[serde(
serialize_with = "rust_decimal::serde::float::serialize",
)]
value: Decimal,
}
let total = Total { value: dec!(1.23) };
let serialized = serde_json::to_string(&total)?;
assert_eq!(r#"{"value":1.23}"#, serialized);
let deserialized: Total = serde_json::from_str(&serialized)?;
assert_eq!(dec!(1.23), deserialized.value);
let deserialized: Total = serde_json::from_str(r#"{"value":"1.23"}"#)?;
assert_eq!(dec!(1.23), deserialized.value); I'd rather keep strong serialization by default with the ability to relax it only if need be. |
My suggestion would be to deprecate the non-with features. They are incorrect as they do not comply to requirement of features being additiive. See: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification This can result in hard-to-find regression bugs when dependent libraries use conflicting features. |
It's a little surprising to me that enabling
serde-arbitrary-precision
by itself does nothingand requires
serde-float
to also be enabled.Would you be open to changing this so that enabling
serde-arbitrary-precision
had the same behaviouras enabling
serde-arbitrary-precision
andserde-float
currently? I'm happy to submit a PR.Or I'd enjoy hearing the rationale behind this :)
On a related topic, it seems that you are trying to promote the use of the
with-*
features instead.The trouble is, unless I'm missing something, these don't seem very ergonomic to use if you have a
Decimal
inside other data structures and require you to essentially implement a whole custom serializer/deserializer.What would be the typical way to use these if you have for example a
struct
field with aHashMap<i64, Vec<Decimal>>
?The text was updated successfully, but these errors were encountered: