Skip to content

Commit

Permalink
Merge #462
Browse files Browse the repository at this point in the history
462: Add `BoolFromInt` adapter r=jonasbb a=jonasbb

Closes  #456

Co-authored-by: Jonas Bushart <jonas@bushart.org>
  • Loading branch information
bors[bot] and jonasbb committed May 28, 2022
2 parents 299a2ba + 82bd5b8 commit d3140e1
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 22 deletions.
17 changes: 16 additions & 1 deletion serde_with/CHANGELOG.md
Expand Up @@ -24,7 +24,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

`time::OffsetDateTime` and `time::PrimitiveDateTime` can now be serialized with the `TimestampSeconds` and related converters.


```rust
// Rust
#[serde_as(as = "serde_with::TimestampMicroSecondsWithFrac<String>")]
Expand All @@ -49,6 +48,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
"rfc_3339": ["1997-11-21T09:55:06-06:00"],
```

* Deserialize `bool` from integers #456 462

Deserialize an integer and convert it into a `bool`.
`BoolFromInt<Strict>` (default) deserializes 0 to `false` and `1` to `true`, other numbers are errors.
`BoolFromInt<Flexible>` deserializes any non-zero as `true`.
Serialization only emits 0/1.

```rust
// Rust
#[serde_as(as = "BoolFromInt")] // BoolFromInt<Strict>
b: bool,

// JSON
"b": 1,
```

### Changed

* Bump MSRV to 1.53, since the new dependency `time` requires that version.
Expand Down
156 changes: 156 additions & 0 deletions serde_with/src/de/impls.rs
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
59 changes: 39 additions & 20 deletions serde_with/src/guide/serde_as_transformations.md
Expand Up @@ -4,26 +4,27 @@ This page lists the transformations implemented in this crate and supported by `

1. [Base64 encode bytes](#base64-encode-bytes)
2. [Big Array support](#big-array-support)
3. [Borrow from the input for `Cow` type](#borrow-from-the-input-for-cow-type)
4. [`Bytes` with more efficiency](#bytes-with-more-efficiency)
5. [Convert to an intermediate type using `Into`](#convert-to-an-intermediate-type-using-into)
6. [Convert to an intermediate type using `TryInto`](#convert-to-an-intermediate-type-using-tryinto)
7. [`Default` from `null`](#default-from-null)
8. [De/Serialize into `Vec`, ignoring errors](#deserialize-into-vec-ignoring-errors)
9. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
10. [`Duration` as seconds](#duration-as-seconds)
11. [Hex encode bytes](#hex-encode-bytes)
12. [Ignore deserialization errors](#ignore-deserialization-errors)
13. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
14. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
15. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
16. [`None` as empty `String`](#none-as-empty-string)
17. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
18. [Pick first successful deserialization](#pick-first-successful-deserialization)
19. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
20. [Value into JSON String](#value-into-json-string)
21. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
22. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
3. [`bool` from integer](#bool-from-integer)
4. [Borrow from the input for `Cow` type](#borrow-from-the-input-for-cow-type)
5. [`Bytes` with more efficiency](#bytes-with-more-efficiency)
6. [Convert to an intermediate type using `Into`](#convert-to-an-intermediate-type-using-into)
7. [Convert to an intermediate type using `TryInto`](#convert-to-an-intermediate-type-using-tryinto)
8. [`Default` from `null`](#default-from-null)
9. [De/Serialize into `Vec`, ignoring errors](#deserialize-into-vec-ignoring-errors)
10. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
11. [`Duration` as seconds](#duration-as-seconds)
12. [Hex encode bytes](#hex-encode-bytes)
13. [Ignore deserialization errors](#ignore-deserialization-errors)
14. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
15. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
16. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
17. [`None` as empty `String`](#none-as-empty-string)
18. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
19. [Pick first successful deserialization](#pick-first-successful-deserialization)
20. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
21. [Value into JSON String](#value-into-json-string)
22. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
23. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)

## Base64 encode bytes

Expand Down Expand Up @@ -57,6 +58,22 @@ value: [[u8; 64]; 33],
"value": [[0,0,0,0,0,...], [0,0,0,...], ...],
```

## `bool` from integer

Deserialize an integer and convert it into a `bool`.
[`BoolFromInt<Strict>`] (default) deserializes 0 to `false` and `1` to `true`, other numbers are errors.
[`BoolFromInt<Flexible>`] deserializes any non-zero as `true`.
Serialization only emits 0/1.

```ignore
// Rust
#[serde_as(as = "BoolFromInt")] // BoolFromInt<Strict>
b: bool,
// JSON
"b": 1,
```

## Borrow from the input for `Cow` type

The types `Cow<'_, str>`, `Cow<'_, [u8]>`, or `Cow<'_, [u8; N]>` can borrow from the input, avoiding extra copies.
Expand Down Expand Up @@ -471,6 +488,8 @@ rfc_3339: OffsetDateTime,
These conversions are availble with the `time_0_3` feature flag.

[`Base64`]: crate::base64::Base64
[`BoolFromInt<Flexible>`]: crate::BoolFromInt
[`BoolFromInt<Strict>`]: crate::BoolFromInt
[`Bytes`]: crate::Bytes
[`chrono::DateTime<Local>`]: chrono_crate::DateTime
[`chrono::DateTime<Utc>`]: chrono_crate::DateTime
Expand Down
49 changes: 49 additions & 0 deletions serde_with/src/lib.rs
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
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

0 comments on commit d3140e1

Please sign in to comment.