Skip to content

Commit

Permalink
Add IfIsHumanReadable
Browse files Browse the repository at this point in the history
  • Loading branch information
irriden committed Jan 28, 2024
1 parent 00382b9 commit 47d974f
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 2 deletions.
5 changes: 5 additions & 0 deletions serde_with/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

* IfIsHumanReadable
Used to specify different transformations for text-based and binary formats.

## [3.5.1] - 2024-01-23

### Fixed
Expand Down
17 changes: 17 additions & 0 deletions serde_with/src/de/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,23 @@ where
}
}

impl<'de, T, H, F> DeserializeAs<'de, T> for IfIsHumanReadable<H, F>
where
H: DeserializeAs<'de, T>,
F: DeserializeAs<'de, T>,
{
fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
H::deserialize_as(deserializer)
} else {
F::deserialize_as(deserializer)
}
}
}

#[cfg(feature = "alloc")]
impl<'de, T, U> DeserializeAs<'de, Vec<T>> for VecSkipError<U>
where
Expand Down
17 changes: 17 additions & 0 deletions serde_with/src/guide/serde_as_transformations.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This page lists the transformations implemented in this crate and supported by `
26. [Value into JSON String](#value-into-json-string)
27. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
28. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
29. [De/Serialize depending on `De/Serializer::is_human_readable`](#deserialize-depending-on-deserializeris_human_readable)

## Base64 encode bytes

Expand Down Expand Up @@ -562,6 +563,21 @@ iso_8601: OffsetDateTime,

These conversions are available with the `time_0_3` feature flag.

## De/Serialize depending on `De/Serializer::is_human_readable`

Used to specify different transformations for text-based and binary formats.

[`IfIsHumanReadable`]

```ignore
// Rust
#[serde_as(as = "serde_with::IfIsHumanReadable<serde_with::DisplayFromStr>")]
value: u128,
// JSON
"value": "340282366920938463463374607431768211455",
```

[`Base64`]: crate::base64::Base64
[`BoolFromInt<Flexible>`]: crate::BoolFromInt
[`BoolFromInt<Strict>`]: crate::BoolFromInt
Expand All @@ -578,6 +594,7 @@ These conversions are available with the `time_0_3` feature flag.
[`EnumMap`]: crate::EnumMap
[`FromInto`]: crate::FromInto
[`Hex`]: crate::hex::Hex
[`IfIsHumanReadable`]: crate::IfIsHumanReadable
[`JsonString`]: crate::json::JsonString
[`KeyValueMap`]: crate::KeyValueMap
[`MapFirstKeyWins`]: crate::MapFirstKeyWins
Expand Down
43 changes: 43 additions & 0 deletions serde_with/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,49 @@ pub struct Same;
/// [`FromStr`]: std::str::FromStr
pub struct DisplayFromStr;

/// Use the first format if [`De/Serializer::is_human_readable`], otherwise use the second
///
/// If the second format is not specified, the normal
/// [`Deserialize`](::serde::Deserialize)/[`Serialize`](::serde::Serialize) traits are used.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "macros")] {
/// # use serde::{Deserialize, Serialize};
/// # use serde_json::json;
/// # use serde_with::{serde_as, DisplayFromStr, IfIsHumanReadable, DurationMilliSeconds, DurationSeconds};
/// use std::time::Duration;
///
/// #[serde_as]
/// #[derive(Deserialize, Serialize)]
/// struct A {
/// #[serde_as(as = "IfIsHumanReadable<DisplayFromStr>")]
/// number: u32,
/// }
/// let x = A {
/// number: 777,
/// };
/// assert_eq!(json!({ "number": "777" }), serde_json::to_value(&x).unwrap());
/// assert_eq!(vec![145, 205, 3, 9], rmp_serde::to_vec(&x).unwrap());
///
/// #[serde_as]
/// #[derive(Deserialize, Serialize)]
/// struct B {
/// #[serde_as(as = "IfIsHumanReadable<DurationMilliSeconds, DurationSeconds>")]
/// duration: Duration,
/// }
/// let x = B {
/// duration: Duration::from_millis(1500),
/// };
/// assert_eq!(json!({ "duration": 1500 }), serde_json::to_value(&x).unwrap());
/// assert_eq!(vec![145, 2], rmp_serde::to_vec(&x).unwrap());
/// # }
/// ```
/// [`De/Serializer::is_human_readable`]: serde::Serializer::is_human_readable
/// [`is_human_readable`]: serde::Serializer::is_human_readable
pub struct IfIsHumanReadable<H, F = Same>(PhantomData<H>, PhantomData<F>);

/// De/Serialize a [`Option<String>`] type while transforming the empty string to [`None`]
///
/// Convert an [`Option<T>`] from/to string using [`FromStr`] and [`Display`](::core::fmt::Display) implementations.
Expand Down
18 changes: 18 additions & 0 deletions serde_with/src/ser/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,24 @@ where
}
}

impl<T, H, F> SerializeAs<T> for IfIsHumanReadable<H, F>
where
T: ?Sized,
H: SerializeAs<T>,
F: SerializeAs<T>,
{
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
H::serialize_as(source, serializer)
} else {
F::serialize_as(source, serializer)
}
}
}

#[cfg(feature = "alloc")]
impl<T, U> SerializeAs<Vec<T>> for VecSkipError<U>
where
Expand Down
21 changes: 19 additions & 2 deletions serde_with/tests/serde_as/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ use expect_test::expect;
use serde::{Deserialize, Serialize};
use serde_with::{
formats::{CommaSeparator, Flexible, Strict},
serde_as, BoolFromInt, BytesOrString, DisplayFromStr, Map, NoneAsEmptyString, OneOrMany, Same,
Seq, StringWithSeparator,
serde_as, BoolFromInt, BytesOrString, DisplayFromStr, IfIsHumanReadable, Map,
NoneAsEmptyString, OneOrMany, Same, Seq, StringWithSeparator,
};
use std::{
collections::HashMap,
Expand Down Expand Up @@ -209,6 +209,23 @@ fn test_display_fromstr() {
is_equal(S(123), expect![[r#""123""#]]);
}

#[test]
fn test_if_is_human_readable() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S(#[serde_as(as = "IfIsHumanReadable<DisplayFromStr>")] u32);

let ser_json = serde_json::to_string(&S(123)).unwrap();
assert_eq!(ser_json, r#""123""#);
let ser_rmp = rmp_serde::to_vec(&S(123)).unwrap();
assert_eq!(ser_rmp, vec![123]);

let de_json: S = serde_json::from_str(r#""123""#).unwrap();
assert_eq!(S(123), de_json);
let de_rmp: S = rmp_serde::from_read(&*vec![123]).unwrap();
assert_eq!(S(123), de_rmp);
}

#[test]
fn test_tuples() {
use std::net::IpAddr;
Expand Down

0 comments on commit 47d974f

Please sign in to comment.