Skip to content

Commit

Permalink
Add BoolFromInt adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasbb committed May 26, 2022
1 parent b3d4156 commit 34fbc97
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 1 deletion.
156 changes: 156 additions & 0 deletions serde_with/src/de/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1349,4 +1349,160 @@ impl<'de> DeserializeAs<'de, Cow<'de, [u8]>> for BorrowCow {
}
}

impl<'de> DeserializeAs<'de, bool> for BoolFromInt<Strict> {
fn deserialize_as<D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
struct U8Visitor;
impl<'de> Visitor<'de> for U8Visitor {
type Value = bool;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an integer 0 or 1")
}

fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: Error,
{
match v {
0 => Ok(false),
1 => Ok(true),
unexp => Err(Error::invalid_value(
Unexpected::Unsigned(unexp as u64),
&"0 or 1",
)),
}
}

fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where
E: Error,
{
match v {
0 => Ok(false),
1 => Ok(true),
unexp => Err(Error::invalid_value(
Unexpected::Signed(unexp as i64),
&"0 or 1",
)),
}
}

fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
match v {
0 => Ok(false),
1 => Ok(true),
unexp => Err(Error::invalid_value(Unexpected::Unsigned(unexp), &"0 or 1")),
}
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
match v {
0 => Ok(false),
1 => Ok(true),
unexp => Err(Error::invalid_value(Unexpected::Signed(unexp), &"0 or 1")),
}
}

fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
where
E: Error,
{
match v {
0 => Ok(false),
1 => Ok(true),
unexp => Err(Error::invalid_value(
Unexpected::Unsigned(unexp as u64),
&"0 or 1",
)),
}
}

fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
where
E: Error,
{
match v {
0 => Ok(false),
1 => Ok(true),
unexp => Err(Error::invalid_value(
Unexpected::Unsigned(unexp as u64),
&"0 or 1",
)),
}
}
}

deserializer.deserialize_u8(U8Visitor)
}
}

impl<'de> DeserializeAs<'de, bool> for BoolFromInt<Flexible> {
fn deserialize_as<D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
struct U8Visitor;
impl<'de> Visitor<'de> for U8Visitor {
type Value = bool;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an integer")
}

fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v != 0)
}

fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v != 0)
}

fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v != 0)
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v != 0)
}

fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v != 0)
}

fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v != 0)
}
}

deserializer.deserialize_u8(U8Visitor)
}
}

// endregion
49 changes: 49 additions & 0 deletions serde_with/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1982,3 +1982,52 @@ pub struct BorrowCow;

#[derive(Copy, Clone, Debug, Default)]
pub struct VecSkipError<T>(PhantomData<T>);

/// Deserialize a boolean from a number
///
/// Deserialize a number (of `u8`) and turn it into a boolean.
/// The adapter supports a [`Strict`](crate::formats::Strict) and [`Flexible`](crate::formats::Flexible) format.
/// In `Strict` mode, the number must be `0` or `1`.
/// All other values produce an error.
/// In `Flexible` mode, the number any non-zero value is converted to `true`.
///
/// During serialization only `0` or `1` are ever emitted.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "macros")] {
/// # use serde::{Deserialize, Serialize};
/// # use serde_json::json;
/// # use serde_with::{serde_as, BoolFromInt};
/// #
/// #[serde_as]
/// # #[derive(Debug, PartialEq)]
/// #[derive(Deserialize, Serialize)]
/// struct Data(#[serde_as(as = "BoolFromInt")] bool);
///
/// let data = Data(true);
/// let j = json!(1);
/// // Ensure serialization and deserialization produce the expected results
/// assert_eq!(j, serde_json::to_value(&data).unwrap());
/// assert_eq!(data, serde_json::from_value(j).unwrap());
///
/// // false maps to 0
/// let data = Data(false);
/// let j = json!(0);
/// assert_eq!(j, serde_json::to_value(&data).unwrap());
/// assert_eq!(data, serde_json::from_value(j).unwrap());
//
/// #[serde_as]
/// # #[derive(Debug, PartialEq)]
/// #[derive(Deserialize, Serialize)]
/// struct Flexible(#[serde_as(as = "BoolFromInt<serde_with::formats::Flexible>")] bool);
///
/// // Flexible turns any non-zero number into true
/// let data = Flexible(true);
/// let j = json!(100);
/// assert_eq!(data, serde_json::from_value(j).unwrap());
/// # }
/// ```
#[derive(Copy, Clone, Debug, Default)]
pub struct BoolFromInt<S: formats::Strictness = formats::Strict>(PhantomData<S>);
9 changes: 9 additions & 0 deletions serde_with/src/ser/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -727,4 +727,13 @@ impl<'a> SerializeAs<Cow<'a, [u8]>> for BorrowCow {
}
}

impl<STRICTNESS: Strictness> SerializeAs<bool> for BoolFromInt<STRICTNESS> {
fn serialize_as<S>(source: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(*source as u8)
}
}

// endregion
70 changes: 69 additions & 1 deletion serde_with/tests/serde_as/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ use core::cell::{Cell, RefCell};
use expect_test::expect;
use serde::{Deserialize, Serialize};
use serde_with::{
formats::Flexible, serde_as, BytesOrString, CommaSeparator, DisplayFromStr, NoneAsEmptyString,
formats::{Flexible, Strict},
serde_as, BoolFromInt, BytesOrString, CommaSeparator, DisplayFromStr, NoneAsEmptyString,
OneOrMany, Same, StringWithSeparator,
};
use std::{
Expand Down Expand Up @@ -1059,3 +1060,70 @@ fn test_borrow_cow_str() {
let s3 = S3::deserialize(&mut deser).unwrap();
assert!(matches!(s3.0, Cow::Borrowed(_)));
}

#[test]
fn test_boolfromint() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S(#[serde_as(as = "BoolFromInt")] bool);

is_equal(S(false), expect![[r#"0"#]]);
is_equal(S(true), expect![[r#"1"#]]);
check_error_deserialization::<S>(
"2",
expect![[r#"invalid value: integer `2`, expected 0 or 1 at line 1 column 1"#]],
);
check_error_deserialization::<S>(
"-100",
expect![[r#"invalid value: integer `-100`, expected 0 or 1 at line 1 column 4"#]],
);
check_error_deserialization::<S>(
"18446744073709551615",
expect![[
r#"invalid value: integer `18446744073709551615`, expected 0 or 1 at line 1 column 20"#
]],
);
check_error_deserialization::<S>(
r#""""#,
expect![[r#"invalid type: string "", expected an integer 0 or 1 at line 1 column 2"#]],
);

#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct SStrict(#[serde_as(as = "BoolFromInt<Strict>")] bool);

is_equal(SStrict(false), expect![[r#"0"#]]);
is_equal(SStrict(true), expect![[r#"1"#]]);
check_error_deserialization::<SStrict>(
"2",
expect![[r#"invalid value: integer `2`, expected 0 or 1 at line 1 column 1"#]],
);
check_error_deserialization::<SStrict>(
"-100",
expect![[r#"invalid value: integer `-100`, expected 0 or 1 at line 1 column 4"#]],
);
check_error_deserialization::<SStrict>(
"18446744073709551615",
expect![[
r#"invalid value: integer `18446744073709551615`, expected 0 or 1 at line 1 column 20"#
]],
);
check_error_deserialization::<SStrict>(
r#""""#,
expect![[r#"invalid type: string "", expected an integer 0 or 1 at line 1 column 2"#]],
);

#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct SFlexible(#[serde_as(as = "BoolFromInt<Flexible>")] bool);

is_equal(SFlexible(false), expect![[r#"0"#]]);
is_equal(SFlexible(true), expect![[r#"1"#]]);
check_deserialization::<SFlexible>(SFlexible(true), "2");
check_deserialization::<SFlexible>(SFlexible(true), "-100");
check_deserialization::<SFlexible>(SFlexible(true), "18446744073709551615");
check_error_deserialization::<SFlexible>(
r#""""#,
expect![[r#"invalid type: string "", expected an integer at line 1 column 2"#]],
);
}

0 comments on commit 34fbc97

Please sign in to comment.