use serde::de::DeserializeOwned;
use serde::{Deserializer, Serialize, Serializer};
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: Serialize,
S: Serializer,
{
match value {
Some(inner) => {
use serde::ser::SerializeMap;
let yaml_value = crate::to_value(inner).map_err(serde::ser::Error::custom)?;
match yaml_value {
crate::Value::Mapping(map) => {
let mut ser_map = serializer.serialize_map(Some(map.len()))?;
for (k, v) in map {
ser_map.serialize_entry(&k, &v)?;
}
ser_map.end()
}
crate::Value::String(s) => {
let mut ser_map = serializer.serialize_map(Some(1))?;
ser_map.serialize_entry(&s, &())?;
ser_map.end()
}
other => {
other.serialize(serializer)
}
}
}
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: DeserializeOwned + 'static,
D: Deserializer<'de>,
{
use serde::Deserialize;
let opt_value: Option<crate::Value> = Option::deserialize(deserializer)?;
match opt_value {
Some(value) => {
let inner: T = crate::from_value(&value).map_err(serde::de::Error::custom)?;
Ok(Some(inner))
}
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum TestEnum {
Unit,
Struct { value: String },
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Container {
name: String,
#[serde(
with = "crate::with::singleton_map_optional",
skip_serializing_if = "Option::is_none",
default
)]
item: Option<TestEnum>,
}
#[test]
fn test_singleton_map_optional_some() {
let container = Container {
name: "test".to_string(),
item: Some(TestEnum::Struct {
value: "hello".to_string(),
}),
};
let yaml = crate::to_string(&container).unwrap();
assert!(yaml.contains("Struct"));
assert!(yaml.contains("value: hello"));
let parsed: Container = crate::from_str(&yaml).unwrap();
assert_eq!(parsed, container);
}
#[test]
fn test_singleton_map_optional_none() {
let container = Container {
name: "test".to_string(),
item: None,
};
let yaml = crate::to_string(&container).unwrap();
assert!(!yaml.contains("item"));
let parsed: Container = crate::from_str(&yaml).unwrap();
assert_eq!(parsed, container);
}
#[test]
fn test_singleton_map_optional_unit_variant() {
let container = Container {
name: "test".to_string(),
item: Some(TestEnum::Unit),
};
let yaml = crate::to_string(&container).unwrap();
assert!(yaml.contains("Unit"));
let parsed: Container = crate::from_str(&yaml).unwrap();
assert_eq!(parsed, container);
}
#[test]
fn test_singleton_map_optional_fallback() {
#[derive(Debug, Serialize)]
struct SeqContainer {
#[serde(with = "crate::with::singleton_map_optional")]
items: Option<Vec<i32>>,
}
let container = SeqContainer {
items: Some(vec![1, 2, 3]),
};
let yaml = crate::to_string(&container).unwrap();
assert!(yaml.contains("1"));
}
#[test]
fn test_singleton_map_optional_serialize_none() {
#[derive(Debug, Serialize)]
struct NoneContainer {
name: String,
#[serde(with = "crate::with::singleton_map_optional")]
item: Option<TestEnum>,
}
let container = NoneContainer {
name: "test".to_string(),
item: None,
};
let yaml = crate::to_string(&container).unwrap();
assert!(yaml.contains("name: test"));
}
#[test]
fn test_singleton_map_optional_deserialize_none() {
#[derive(Debug, Deserialize, PartialEq)]
struct NoneContainer {
name: String,
#[serde(with = "crate::with::singleton_map_optional", default)]
item: Option<TestEnum>,
}
let yaml = "name: test\nitem: null\n";
let parsed: NoneContainer = crate::from_str(yaml).unwrap();
assert_eq!(parsed.name, "test");
assert!(parsed.item.is_none());
}
}