Skip to content

Commit

Permalink
Serialize Options as untagged or explicit
Browse files Browse the repository at this point in the history
Add new conversions UntaggedOption and ExplicitOption.
UntaggedOption serializes the enum but untagged, sidestepping the
default Option rules.
ExplicitOption serializes the Option as any other enum.

Fixes #365
  • Loading branch information
jonasbb committed Nov 5, 2022
1 parent dc6fea6 commit ce927b0
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 2 deletions.
45 changes: 45 additions & 0 deletions serde_with/src/de/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1921,4 +1921,49 @@ impl<'de> DeserializeAs<'de, bool> for BoolFromInt<Flexible> {
}
}

impl<'de, T, U> DeserializeAs<'de, Option<T>> for ExplicitOption<U>
where
U: DeserializeAs<'de, T>,
{
fn deserialize_as<D>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(serde::Deserialize)]
enum Option<T> {
None,
Some(T),
}

let eo: Option<DeserializeAsWrap<T, U>> = Deserialize::deserialize(deserializer)?;
Ok(match eo {
Option::None => None,
Option::Some(value) => Some(value.into_inner()),
})
}
}

impl<'de, T, U> DeserializeAs<'de, Option<T>> for UntaggedOption<U>
where
U: DeserializeAs<'de, T>,
{
fn deserialize_as<D>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum Option<T> {
None,
Some(T),
}

let eo: Option<DeserializeAsWrap<T, U>> = Deserialize::deserialize(deserializer)?;
Ok(match eo {
Option::None => None,
Option::Some(value) => Some(value.into_inner()),
})
}
}

// endregion
4 changes: 4 additions & 0 deletions serde_with/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2081,3 +2081,7 @@ pub struct BoolFromInt<S: formats::Strictness = formats::Strict>(PhantomData<S>)
/// [`Separator`]: crate::formats::Separator
/// [`serde_as`]: crate::guide::serde_as
pub struct StringWithSeparator<Sep, T>(PhantomData<(Sep, T)>);

pub struct ExplicitOption<T = Same>(PhantomData<T>);

pub struct UntaggedOption<T = Same>(PhantomData<T>);
35 changes: 35 additions & 0 deletions serde_with/src/ser/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -896,4 +896,39 @@ impl<STRICTNESS: Strictness> SerializeAs<bool> for BoolFromInt<STRICTNESS> {
}
}

impl<T, U> SerializeAs<Option<T>> for ExplicitOption<U>
where
U: SerializeAs<T>,
{
fn serialize_as<S>(source: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match source {
None => serializer.serialize_unit_variant("Option", 0, "None"),
Some(value) => serializer.serialize_newtype_variant(
"Option",
1,
"Some",
&SerializeAsWrap::<T, U>::new(value),
),
}
}
}

impl<T, U> SerializeAs<Option<T>> for UntaggedOption<U>
where
U: SerializeAs<T>,
{
fn serialize_as<S>(source: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match source {
None => serializer.serialize_unit(),
Some(value) => U::serialize_as(value, serializer),
}
}
}

// endregion
90 changes: 88 additions & 2 deletions serde_with/tests/serde_as/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use expect_test::expect;
use serde::{Deserialize, Serialize};
use serde_with::{
formats::{CommaSeparator, Flexible, Strict},
serde_as, BoolFromInt, BytesOrString, DisplayFromStr, NoneAsEmptyString, OneOrMany, Same,
StringWithSeparator,
serde_as, BoolFromInt, BytesOrString, DisplayFromStr, ExplicitOption, NoneAsEmptyString,
OneOrMany, Same, StringWithSeparator, UntaggedOption,
};
use std::{
collections::HashMap,
Expand Down Expand Up @@ -1130,3 +1130,89 @@ fn test_boolfromint() {
expect![[r#"invalid type: string "", expected an integer at line 1 column 2"#]],
);
}

#[test]
fn test_explicit_option() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(transparent)]
struct S1(#[serde_as(as = "ExplicitOption")] Option<i32>);

is_equal(S1(None), expect![[r#""None""#]]);
is_equal(
S1(Some(123)),
expect![[r#"
{
"Some": 123
}"#]],
);
check_error_deserialization::<S1>("true", expect!["expected value at line 1 column 1"]);
check_error_deserialization::<S1>(
r#""foobar""#,
expect!["unknown variant `foobar`, expected `None` or `Some` at line 1 column 8"],
);
check_error_deserialization::<S1>(r#"{}"#, expect!["expected value at line 1 column 2"]);

#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(transparent)]
struct S2(#[serde_as(as = "ExplicitOption<DisplayFromStr>")] Option<i32>);

is_equal(S2(None), expect![[r#""None""#]]);
is_equal(
S2(Some(123)),
expect![[r#"
{
"Some": "123"
}"#]],
);
check_error_deserialization::<S2>("true", expect!["expected value at line 1 column 1"]);
check_error_deserialization::<S2>(
r#""foobar""#,
expect!["unknown variant `foobar`, expected `None` or `Some` at line 1 column 8"],
);
check_error_deserialization::<S2>(r#"{}"#, expect!["expected value at line 1 column 2"]);
}

#[test]
fn test_untagged_option() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(transparent)]
struct S1(#[serde_as(as = "UntaggedOption")] Option<i32>);

is_equal(S1(None), expect!["null"]);
is_equal(S1(Some(123)), expect!["123"]);
check_error_deserialization::<S1>(
"true",
expect!["data did not match any variant of untagged enum Option"],
);
check_error_deserialization::<S1>(
r#""foobar""#,
expect!["data did not match any variant of untagged enum Option"],
);
check_error_deserialization::<S1>(
r#"{}"#,
expect!["data did not match any variant of untagged enum Option"],
);

#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(transparent)]
struct S2(#[serde_as(as = "UntaggedOption<DisplayFromStr>")] Option<i32>);

is_equal(S2(None), expect!["null"]);
is_equal(S2(Some(123)), expect![[r#""123""#]]);
check_error_deserialization::<S2>(
"true",
expect!["data did not match any variant of untagged enum Option"],
);
check_error_deserialization::<S2>(
r#""foobar""#,
expect!["data did not match any variant of untagged enum Option"],
);
check_error_deserialization::<S2>(
r#"{}"#,
expect!["data did not match any variant of untagged enum Option"],
);
}

0 comments on commit ce927b0

Please sign in to comment.