diff --git a/.circleci/config.yml b/.circleci/config.yml index bf4eb2b..4bfb127 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,6 +107,7 @@ workflows: rust-features: - "--features='serde'" - "--features='serde,string-only'" + - "--features='serde,arbitrary-precision'" - build-and-test: name: build-and-test:no-default-features diff --git a/Cargo.toml b/Cargo.toml index 6fdfbea..b783e07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,14 @@ num-bigint = { version = "0.4", default-features = false } num-integer = { version = "0.1", default-features = false } num-traits = { version = "0.2", default-features = false } serde = { version = "1.0", optional = true, default-features = false } +# `arbitrary-precision` is specifically an interaction with the similarly named feature in `serde_json`. We need it when we want to support it. +# +# The version is set to the minimum needed for the `arbitrary_precision` feature +serde_json = { version = "1.0.108", optional = true, default-features = false, features = ["arbitrary_precision", "std"] } [dev-dependencies] paste = "1" -serde_json = "<1.0.101" +serde_json = { version = "1" } siphasher = { version = "0.3.10", default-features = false } # The following dev-dependencies are only required for benchmarking # (use the `benchmark-bigdecimal` script to uncomment these and run benchmarks) @@ -44,6 +48,7 @@ autocfg = "1" [features] default = ["std"] +arbitrary-precision = ["serde", "serde_json", "std"] string-only = [] std = ["num-bigint/std", "num-integer/std", "num-traits/std"] diff --git a/src/impl_serde.rs b/src/impl_serde.rs new file mode 100644 index 0000000..2d3d4dc --- /dev/null +++ b/src/impl_serde.rs @@ -0,0 +1,293 @@ +//! +//! Support for serde implementations +//! +use crate::{BigDecimal, fmt, FromStr, TryFrom}; +use serde::{de, ser}; + +#[allow(unused_imports)] +use num_traits::FromPrimitive; + +/// Serialize/deserialize [`BigDecimal`] as arbitrary precision numbers in JSON using the `arbitrary_precision` feature within `serde_json`. +/// +// The following example is ignored as it requires derives which we don't import and aren't compatible +// with our locked versions of rust due to proc_macro2. +/// ```ignore +/// # extern crate serde; +/// # use serde::{Serialize, Deserialize}; +/// # use bigdecimal::BigDecimal; +/// # use std::str::FromStr; +/// +/// #[derive(Serialize, Deserialize)] +/// pub struct ArbitraryExample { +/// #[serde(with = "bigdecimal::impl_serde::arbitrary_precision")] +/// value: BigDecimal, +/// } +/// +/// let value = ArbitraryExample { value: BigDecimal::from_str("123.400").unwrap() }; +/// assert_eq!( +/// &serde_json::to_string(&value).unwrap(), +/// r#"{"value":123.400}"# +/// ); +/// ``` +#[cfg(feature = "arbitrary-precision")] +pub mod arbitrary_precision { + use crate::{BigDecimal, FromStr, stdlib::string::ToString}; + use serde::{Serialize, Deserialize}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + serde_json::Number::deserialize(deserializer)?.to_string().parse().map_err(serde::de::Error::custom) + + } + + pub fn serialize(value: &BigDecimal, serializer: S) -> Result + where + S: serde::Serializer, + { + serde_json::Number::from_str(&value.to_string()) + .map_err(serde::ser::Error::custom)? + .serialize(serializer) + } +} + +/// Serialize/deserialize [`Option`] as arbitrary precision numbers in JSON using the `arbitrary_precision` feature within `serde_json`. +/// +// The following example is ignored as it requires derives which we don't import and aren't compatible +// with our locked versions of rust due to proc_macro2. +/// ```ignore +/// # extern crate serde; +/// # use serde::{Serialize, Deserialize}; +/// # use bigdecimal::BigDecimal; +/// # use std::str::FromStr; +/// +/// #[derive(Serialize, Deserialize)] +/// pub struct ArbitraryExample { +/// #[serde(with = "bigdecimal::impl_serde::arbitrary_precision_option")] +/// value: Option, +/// } +/// +/// let value = ArbitraryExample { value: Some(BigDecimal::from_str("123.400").unwrap()) }; +/// assert_eq!( +/// &serde_json::to_string(&value).unwrap(), +/// r#"{"value":123.400}"# +/// ); +/// +/// let value = ArbitraryExample { value: None }; +/// assert_eq!( +/// &serde_json::to_string(&value).unwrap(), +/// r#"{"value":null}"# +/// ); +/// ``` +#[cfg(feature = "arbitrary-precision")] +pub mod arbitrary_precision_option { + use crate::{BigDecimal, FromStr, stdlib::string::ToString}; + use serde::{Serialize, Deserialize}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::de::Deserializer<'de>, + { + Option::::deserialize(deserializer)?.map(|num| num.to_string().parse().map_err(serde::de::Error::custom)).transpose() + + } + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: serde::Serializer, + { + match *value { + Some(ref decimal) => serde_json::Number::from_str(&decimal.to_string()) + .map_err(serde::ser::Error::custom)? + .serialize(serializer), + None => serializer.serialize_none(), + } + } +} + +impl ser::Serialize for BigDecimal { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.collect_str(&self) + } +} + +struct BigDecimalVisitor; + +impl<'de> de::Visitor<'de> for BigDecimalVisitor { + type Value = BigDecimal; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a number or formatted decimal string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + BigDecimal::from_str(value).map_err(|err| E::custom(format!("{}", err))) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(BigDecimal::from(value)) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(BigDecimal::from(value)) + } + + fn visit_f64(self, value: f64) -> Result + where + E: de::Error, + { + BigDecimal::try_from(value).map_err(|err| E::custom(format!("{}", err))) + } +} + +#[cfg(not(feature = "string-only"))] +impl<'de> de::Deserialize<'de> for BigDecimal { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_any(BigDecimalVisitor) + } +} + +#[cfg(feature = "string-only")] +impl<'de> de::Deserialize<'de> for BigDecimal { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_str(BigDecimalVisitor) + } +} + +#[cfg(test)] +mod test { +extern crate serde_json; + use crate::{BigDecimal, FromStr}; + use serde::Deserialize; + + #[test] + fn test_serde_serialize() { + let vals = vec![ + ("1.0", "1.0"), + ("0.5", "0.5"), + ("50", "50"), + ("50000", "50000"), + ("1e-3", "0.001"), + ("1e12", "1000000000000"), + ("0.25", "0.25"), + ("12.34", "12.34"), + ("0.15625", "0.15625"), + ("0.3333333333333333", "0.3333333333333333"), + ("3.141592653589793", "3.141592653589793"), + ("94247.77960769380", "94247.77960769380"), + ("10.99", "10.99"), + ("12.0010", "12.0010"), + ]; + for (s, v) in vals { + let expected = format!("\"{}\"", v); + let value = serde_json::to_string(&BigDecimal::from_str(s).unwrap()).unwrap(); + assert_eq!(expected, value); + } + } + + #[test] + fn test_serde_deserialize_str() { + let vals = vec![ + ("1.0", "1.0"), + ("0.5", "0.5"), + ("50", "50"), + ("50000", "50000"), + ("1e-3", "0.001"), + ("1e12", "1000000000000"), + ("0.25", "0.25"), + ("12.34", "12.34"), + ("0.15625", "0.15625"), + ("0.3333333333333333", "0.3333333333333333"), + ("3.141592653589793", "3.141592653589793"), + ("94247.77960769380", "94247.77960769380"), + ("10.99", "10.99"), + ("12.0010", "12.0010"), + ]; + for (s, v) in vals { + let expected = BigDecimal::from_str(v).unwrap(); + let value: BigDecimal = serde_json::from_str(&format!("\"{}\"", s)).unwrap(); + assert_eq!(expected, value); + } + } + + #[test] + #[cfg(not(feature = "string-only"))] + fn test_serde_deserialize_int() { + use crate::FromPrimitive; + + let vals = vec![0, 1, 81516161, -370, -8, -99999999999]; + for n in vals { + let expected = BigDecimal::from_i64(n).unwrap(); + let value: BigDecimal = serde_json::from_str(&serde_json::to_string(&n).unwrap()).unwrap(); + assert_eq!(expected, value); + } + } + + #[test] + #[cfg(not(any(feature = "string-only", feature = "arbitrary-precision")))] + fn test_serde_deserialize_f64() { + use crate::{FromPrimitive,stdlib::f64::consts::PI}; + + let vals = vec![ + 1.0, + 0.5, + 0.25, + 50.0, + 50000., + 0.001, + 12.34, + 5.0 * 0.03125, + PI, + PI * 10000.0, + PI * 30000.0, + ]; + for n in vals { + let expected = BigDecimal::from_f64(n).unwrap(); + let value: BigDecimal = serde_json::from_str(&serde_json::to_string(&n).unwrap()).unwrap(); + assert_eq!(expected, value); + } + } + + /// Not a great test but demonstrates why `arbitrary-precision` exists. + #[test] + #[cfg(not(feature = "arbitrary-precision"))] + fn test_normal_precision() { + let json = r#"0.1"#; + let expected = BigDecimal::from_str("0.1").expect("should parse 0.1 as BigDecimal"); + let deser: BigDecimal = serde_json::from_str(json).expect("should parse JSON"); + + // 0.1 is directly representable in `BigDecimal`, but not `f64` so the default deserialization fails. + assert_ne!(expected, deser); + } + + #[test] + #[cfg(feature = "arbitrary-precision")] + fn test_arbitrary_precision() { + use crate::impl_serde::arbitrary_precision; + + let json = r#"0.1"#; + let expected = BigDecimal::from_str("0.1").expect("should parse 0.1 as BigDecimal"); + let deser = arbitrary_precision::deserialize(&mut serde_json::Deserializer::from_str(json)).expect("should parse JSON"); + + assert_eq!(expected, deser); + } +} diff --git a/src/lib.rs b/src/lib.rs index e461a6a..72202cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,6 +112,10 @@ mod impl_cmp; // Implementations of num_traits mod impl_num; +// Implementations for deserializations and serializations +#[cfg(feature = "serde")] +pub mod impl_serde; + // construct BigDecimals from strings and floats mod parsing; @@ -1352,171 +1356,6 @@ impl<'a> From<&'a BigInt> for BigDecimalRef<'a> { } } - -/// Tools to help serializing/deserializing `BigDecimal`s -#[cfg(feature = "serde")] -mod bigdecimal_serde { - use super::*; - use serde::{de, ser}; - - #[allow(unused_imports)] - use num_traits::FromPrimitive; - - impl ser::Serialize for BigDecimal { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.collect_str(&self) - } - } - - struct BigDecimalVisitor; - - impl<'de> de::Visitor<'de> for BigDecimalVisitor { - type Value = BigDecimal; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a number or formatted decimal string") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - BigDecimal::from_str(value).map_err(|err| E::custom(format!("{}", err))) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(BigDecimal::from(value)) - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - Ok(BigDecimal::from(value)) - } - - fn visit_f64(self, value: f64) -> Result - where - E: de::Error, - { - BigDecimal::try_from(value).map_err(|err| E::custom(format!("{}", err))) - } - } - - #[cfg(not(feature = "string-only"))] - impl<'de> de::Deserialize<'de> for BigDecimal { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_any(BigDecimalVisitor) - } - } - - #[cfg(feature = "string-only")] - impl<'de> de::Deserialize<'de> for BigDecimal { - fn deserialize(d: D) -> Result - where - D: de::Deserializer<'de>, - { - d.deserialize_str(BigDecimalVisitor) - } - } - - #[cfg(test)] - extern crate serde_json; - - #[test] - fn test_serde_serialize() { - let vals = vec![ - ("1.0", "1.0"), - ("0.5", "0.5"), - ("50", "50"), - ("50000", "50000"), - ("1e-3", "0.001"), - ("1e12", "1000000000000"), - ("0.25", "0.25"), - ("12.34", "12.34"), - ("0.15625", "0.15625"), - ("0.3333333333333333", "0.3333333333333333"), - ("3.141592653589793", "3.141592653589793"), - ("94247.77960769380", "94247.77960769380"), - ("10.99", "10.99"), - ("12.0010", "12.0010"), - ]; - for (s, v) in vals { - let expected = format!("\"{}\"", v); - let value = serde_json::to_string(&BigDecimal::from_str(s).unwrap()).unwrap(); - assert_eq!(expected, value); - } - } - - #[test] - fn test_serde_deserialize_str() { - let vals = vec![ - ("1.0", "1.0"), - ("0.5", "0.5"), - ("50", "50"), - ("50000", "50000"), - ("1e-3", "0.001"), - ("1e12", "1000000000000"), - ("0.25", "0.25"), - ("12.34", "12.34"), - ("0.15625", "0.15625"), - ("0.3333333333333333", "0.3333333333333333"), - ("3.141592653589793", "3.141592653589793"), - ("94247.77960769380", "94247.77960769380"), - ("10.99", "10.99"), - ("12.0010", "12.0010"), - ]; - for (s, v) in vals { - let expected = BigDecimal::from_str(v).unwrap(); - let value: BigDecimal = serde_json::from_str(&format!("\"{}\"", s)).unwrap(); - assert_eq!(expected, value); - } - } - - #[test] - #[cfg(not(feature = "string-only"))] - fn test_serde_deserialize_int() { - let vals = vec![0, 1, 81516161, -370, -8, -99999999999]; - for n in vals { - let expected = BigDecimal::from_i64(n).unwrap(); - let value: BigDecimal = serde_json::from_str(&serde_json::to_string(&n).unwrap()).unwrap(); - assert_eq!(expected, value); - } - } - - #[test] - #[cfg(not(feature = "string-only"))] - fn test_serde_deserialize_f64() { - let vals = vec![ - 1.0, - 0.5, - 0.25, - 50.0, - 50000., - 0.001, - 12.34, - 5.0 * 0.03125, - stdlib::f64::consts::PI, - stdlib::f64::consts::PI * 10000.0, - stdlib::f64::consts::PI * 30000.0, - ]; - for n in vals { - let expected = BigDecimal::from_f64(n).unwrap(); - let value: BigDecimal = serde_json::from_str(&serde_json::to_string(&n).unwrap()).unwrap(); - assert_eq!(expected, value); - } - } -} - #[rustfmt::skip] #[cfg(test)] #[allow(non_snake_case)]