Skip to content

Commit

Permalink
Merge #487
Browse files Browse the repository at this point in the history
487: Make the `JsonString` smarter r=jonasbb a=jonasbb

This allow nested application of `serde_as` logic on the inner value.

```rust
// Rust
value: BTreeMap<[u8; 2], u32>,

// JSON
{"value":"[[\"[1,2]\",3],[\"[4,5]\",6]]"}
```

Closes #485

bors r+

Co-authored-by: Jonas Bushart <jonas@bushart.org>
  • Loading branch information
bors[bot] and jonasbb authored Jul 1, 2022
2 parents 6969abf + 69ed4a1 commit 49ebe72
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 16 deletions.
14 changes: 14 additions & 0 deletions serde_with/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

* Make `JsonString<T>` smarter by allowing nesting `serde_as` definitions.
This allows applying custom serialization logic, before the value gets converted into a JSON string.

```rust
// Rust
#[serde_as(as = "JsonString<Vec<(JsonString, _)>>")]
value: BTreeMap<[u8; 2], u32>,

// JSON
{"value":"[[\"[1,2]\",3],[\"[4,5]\",6]]"}
```

## [2.0.0-rc.0] - 2022-06-29

### Changed
Expand Down
8 changes: 8 additions & 0 deletions serde_with/src/guide/serde_as_transformations.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@ value: OtherStruct,
"value": "{\"value\":5}",
```

```ignore
#[serde_as(as = "JsonString<Vec<(JsonString, _)>>")]
value: BTreeMap<[u8; 2], u32>,
// JSON
{"value":"[[\"[1,2]\",3],[\"[4,5]\",6]]"}
```

## `Vec` of tuples to `Maps`

```ignore
Expand Down
63 changes: 48 additions & 15 deletions serde_with/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
//!
//! This modules is only available when using the `json` feature of the crate.
use crate::{de::DeserializeAs, ser::SerializeAs};
use crate::{
de::{DeserializeAs, DeserializeAsWrap},
ser::{SerializeAs, SerializeAsWrap},
Same,
};
use core::{fmt, marker::PhantomData};
use serde::{
de,
de::{DeserializeOwned, Deserializer, Visitor},
de::{Deserializer, Visitor},
ser,
ser::{Serialize, Serializer},
ser::Serializer,
};

/// Serialize value as string containing JSON
Expand Down Expand Up @@ -51,34 +55,61 @@ use serde::{
/// );
/// # }
/// ```
pub struct JsonString;
///
/// The `JsonString` converter takes a type argument, which allows altering the serialization behavior of the inner value, before it gets turned into a JSON string.
///
/// ```
/// # #[cfg(feature = "macros")] {
/// # use serde::{Deserialize, Serialize};
/// # use serde_with::{serde_as, json::JsonString};
/// # use std::collections::BTreeMap;
/// #
/// #[serde_as]
/// #[derive(Debug, Serialize, Deserialize, PartialEq)]
/// struct Struct {
/// #[serde_as(as = "JsonString<Vec<(JsonString, _)>>")]
/// value: BTreeMap<[u8; 2], u32>,
/// }
///
/// let value = Struct {
/// value: BTreeMap::from([([1, 2], 3), ([4, 5], 6)]),
/// };
/// assert_eq!(
/// r#"{"value":"[[\"[1,2]\",3],[\"[4,5]\",6]]"}"#,
/// serde_json::to_string(&value).unwrap()
/// );
/// # }
/// ```
pub struct JsonString<T = Same>(PhantomData<T>);

impl<T> SerializeAs<T> for JsonString
impl<T, TAs> SerializeAs<T> for JsonString<TAs>
where
T: Serialize,
TAs: SerializeAs<T>,
{
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = serde_json::to_string(source).map_err(ser::Error::custom)?;
serializer.serialize_str(&*s)
serializer.serialize_str(
&serde_json::to_string(&SerializeAsWrap::<T, TAs>::new(source))
.map_err(ser::Error::custom)?,
)
}
}

impl<'de, T> DeserializeAs<'de, T> for JsonString
impl<'de, T, TAs> DeserializeAs<'de, T> for JsonString<TAs>
where
T: DeserializeOwned,
TAs: for<'a> DeserializeAs<'a, T>,
{
fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
{
struct Helper<S: DeserializeOwned>(PhantomData<S>);
struct Helper<S, SAs>(PhantomData<(S, SAs)>);

impl<'de, S> Visitor<'de> for Helper<S>
impl<'de, S, SAs> Visitor<'de> for Helper<S, SAs>
where
S: DeserializeOwned,
SAs: for<'a> DeserializeAs<'a, S>,
{
type Value = S;

Expand All @@ -90,10 +121,12 @@ where
where
E: de::Error,
{
serde_json::from_str(value).map_err(de::Error::custom)
serde_json::from_str(value)
.map(DeserializeAsWrap::<S, SAs>::into_inner)
.map_err(de::Error::custom)
}
}

deserializer.deserialize_str(Helper(PhantomData))
deserializer.deserialize_str(Helper::<T, TAs>(PhantomData))
}
}
23 changes: 22 additions & 1 deletion serde_with/tests/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use crate::utils::is_equal;
use expect_test::expect;
use serde::{Deserialize, Serialize};
use serde_with::{json::JsonString, serde_as, DisplayFromStr};
use std::collections::BTreeMap;

#[test]
fn test_nested_json() {
fn test_jsonstring() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Struct {
Expand All @@ -39,3 +40,23 @@ fn test_nested_json() {
}"#]],
);
}

#[test]
fn test_jsonstring_nested() {
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Struct {
#[serde_as(as = "JsonString<Vec<(JsonString, _)>>")]
value: BTreeMap<[u8; 2], u32>,
}

is_equal(
Struct {
value: BTreeMap::from([([1, 2], 3), ([4, 5], 6)]),
},
expect![[r#"
{
"value": "[[\"[1,2]\",3],[\"[4,5]\",6]]"
}"#]],
);
}

0 comments on commit 49ebe72

Please sign in to comment.