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

Support Option<T> serialization/deserialization using serde-with-* #524

Merged
merged 2 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ pub struct FloatExample {
value: Decimal,
}
```
```rust
#[derive(Serialize, Deserialize)]
pub struct OptionFloatExample {
#[serde(with = "rust_decimal::serde::float_option")]
value: Option<Decimal>,
}
```

### `serde-with-str`

Expand All @@ -222,6 +229,13 @@ pub struct StrExample {
value: Decimal,
}
```
```rust
#[derive(Serialize, Deserialize)]
pub struct OptionStrExample {
#[serde(with = "rust_decimal::serde::str_option")]
value: Option<Decimal>,
}
```

### `serde-with-arbitrary-precision`

Expand All @@ -234,6 +248,13 @@ pub struct ArbitraryExample {
value: Decimal,
}
```
```rust
#[derive(Serialize, Deserialize)]
pub struct OptionArbitraryExample {
#[serde(with = "rust_decimal::serde::arbitrary_precision_option")]
value: Option<Decimal>,
}
```

### `std`

Expand Down
245 changes: 245 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,56 @@ pub mod arbitrary_precision {
}
}

/// Serialize/deserialize optional Decimals as arbitrary precision numbers in JSON using the `arbitrary_precision` feature within `serde_json`.
///
/// ```
/// # use serde::{Serialize, Deserialize};
/// # use rust_decimal::Decimal;
/// # use std::str::FromStr;
///
/// #[derive(Serialize, Deserialize)]
/// pub struct ArbitraryExample {
/// #[serde(with = "rust_decimal::serde::arbitrary_precision_option")]
/// value: Option<Decimal>,
/// }
///
/// let value = ArbitraryExample { value: Some(Decimal::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 = "serde-with-arbitrary-precision")]
pub mod arbitrary_precision_option {
use super::*;
use serde::Serialize;

pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_option(OptionDecimalVisitor)
}

pub fn serialize<S>(value: &Option<Decimal>, serializer: S) -> Result<S::Ok, S::Error>
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(),
}
}
}

/// Serialize/deserialize Decimals as floats.
///
/// ```
Expand Down Expand Up @@ -85,6 +135,57 @@ pub mod float {
}
}

/// Serialize/deserialize optional Decimals as floats.
///
/// ```
/// # use serde::{Serialize, Deserialize};
/// # use rust_decimal::Decimal;
/// # use std::str::FromStr;
///
/// #[derive(Serialize, Deserialize)]
/// pub struct FloatExample {
/// #[serde(with = "rust_decimal::serde::float_option")]
/// value: Option<Decimal>,
/// }
///
/// let value = FloatExample { value: Some(Decimal::from_str("123.400").unwrap()) };
/// assert_eq!(
/// &serde_json::to_string(&value).unwrap(),
/// r#"{"value":123.4}"#
/// );
///
/// let value = FloatExample { value: None };
/// assert_eq!(
/// &serde_json::to_string(&value).unwrap(),
/// r#"{"value":null}"#
/// );
/// ```
#[cfg(feature = "serde-with-float")]
pub mod float_option {
use super::*;
use serde::Serialize;

pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_option(OptionDecimalVisitor)
}

pub fn serialize<S>(value: &Option<Decimal>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *value {
Some(ref decimal) => {
use num_traits::ToPrimitive;
decimal.to_f64().unwrap().serialize(serializer)
}
None => serializer.serialize_none(),
}
}
}

/// Serialize/deserialize Decimals as strings. This is particularly useful when using binary encoding formats.
///
/// ```
Expand All @@ -103,6 +204,7 @@ pub mod float {
/// &serde_json::to_string(&value).unwrap(),
/// r#"{"value":"123.400"}"#
/// );
///
/// ```
#[cfg(feature = "serde-with-str")]
pub mod str {
Expand All @@ -124,6 +226,56 @@ pub mod str {
}
}

/// Serialize/deserialize optional Decimals as strings. This is particularly useful when using binary encoding formats.
///
/// ```
/// # use serde::{Serialize, Deserialize};
/// # use rust_decimal::Decimal;
/// # use std::str::FromStr;
///
/// #[derive(Serialize, Deserialize)]
/// pub struct StringExample {
/// #[serde(with = "rust_decimal::serde::str_option")]
/// value: Option<Decimal>,
/// }
///
/// let value = StringExample { value: Some(Decimal::from_str("123.400").unwrap()) };
/// assert_eq!(
/// &serde_json::to_string(&value).unwrap(),
/// r#"{"value":"123.400"}"#
/// );
///
/// let value = StringExample { value: None };
/// assert_eq!(
/// &serde_json::to_string(&value).unwrap(),
/// r#"{"value":null}"#
/// );
/// ```
#[cfg(feature = "serde-with-str")]
pub mod str_option {
use super::*;

pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_option(OptionDecimalVisitor)
}

pub fn serialize<S>(value: &Option<Decimal>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *value {
Some(ref decimal) => {
let decimal = crate::str::to_str_internal(decimal, true, None);
serializer.serialize_str(decimal.0.as_ref())
}
None => serializer.serialize_none(),
}
}
}

#[cfg(not(feature = "serde-str"))]
impl<'de> serde::Deserialize<'de> for Decimal {
fn deserialize<D>(deserializer: D) -> Result<Decimal, D::Error>
Expand Down Expand Up @@ -218,6 +370,30 @@ impl<'de> serde::de::Visitor<'de> for DecimalVisitor {
}
}

struct OptionDecimalVisitor;

impl<'de> serde::de::Visitor<'de> for OptionDecimalVisitor {
type Value = Option<Decimal>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a Decimal type representing a fixed-point number")
}

fn visit_none<E>(self) -> Result<Option<Decimal>, E>
where
E: serde::de::Error,
{
Ok(None)
}

fn visit_some<D>(self, d: D) -> Result<Option<Decimal>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
d.deserialize_any(DecimalVisitor).map(Some)
}
}

#[cfg(feature = "serde-with-arbitrary-precision")]
struct DecimalKey;

Expand Down Expand Up @@ -581,4 +757,73 @@ mod test {
assert_eq!(expected, decoded.value);
}
}

#[test]
#[cfg(feature = "serde-with-str")]
fn with_str_optional() {
#[derive(Serialize, Deserialize)]
pub struct StringExample {
#[serde(with = "crate::serde::str_option")]
value: Option<Decimal>,
}

let original = StringExample {
value: Some(Decimal::from_str("123.400").unwrap()),
};
assert_eq!(&serde_json::to_string(&original).unwrap(), r#"{"value":"123.400"}"#);
let deserialized: StringExample = serde_json::from_str(r#"{"value":"123.400"}"#).unwrap();
assert_eq!(deserialized.value, original.value);

// Null tests
let original = StringExample { value: None };
assert_eq!(&serde_json::to_string(&original).unwrap(), r#"{"value":null}"#);
let deserialized: StringExample = serde_json::from_str(r#"{"value":null}"#).unwrap();
assert_eq!(deserialized.value, original.value);
}

#[test]
#[cfg(feature = "serde-with-float")]
fn with_float_optional() {
#[derive(Serialize, Deserialize)]
pub struct StringExample {
#[serde(with = "crate::serde::float_option")]
value: Option<Decimal>,
}

let original = StringExample {
value: Some(Decimal::from_str("123.400").unwrap()),
};
assert_eq!(&serde_json::to_string(&original).unwrap(), r#"{"value":123.4}"#);
let deserialized: StringExample = serde_json::from_str(r#"{"value":123.4}"#).unwrap();
assert_eq!(deserialized.value, original.value);

// Null tests
let original = StringExample { value: None };
assert_eq!(&serde_json::to_string(&original).unwrap(), r#"{"value":null}"#);
let deserialized: StringExample = serde_json::from_str(r#"{"value":null}"#).unwrap();
assert_eq!(deserialized.value, original.value);
}

#[test]
#[cfg(feature = "serde-with-arbitrary-precision")]
fn with_arbitrary_precision_optional() {
#[derive(Serialize, Deserialize)]
pub struct StringExample {
#[serde(with = "crate::serde::arbitrary_precision_option")]
value: Option<Decimal>,
}

let original = StringExample {
value: Some(Decimal::from_str("123.400").unwrap()),
};
assert_eq!(&serde_json::to_string(&original).unwrap(), r#"{"value":123.400}"#);
let deserialized: StringExample = serde_json::from_str(r#"{"value":123.400}"#).unwrap();
assert_eq!(deserialized.value, original.value);

// Null tests
let original = StringExample { value: None };
assert_eq!(&serde_json::to_string(&original).unwrap(), r#"{"value":null}"#);
let deserialized: StringExample = serde_json::from_str(r#"{"value":null}"#).unwrap();
assert_eq!(deserialized.value, original.value);
}
}