use serde::de::{self, Deserialize, Deserializer};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Variant<T>(pub T);
impl<T> Variant<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<'de, T> Deserialize<'de> for Variant<T>
where
T: serde::de::DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
let inner = match value {
Value::Object(mut map) if map.contains_key("t") && map.contains_key("v") => {
map.remove("v").unwrap_or(Value::Null)
}
other => other,
};
serde_json::from_value(inner)
.map(Variant)
.map_err(de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct VariantU64(pub u64);
impl<'de> Deserialize<'de> for VariantU64 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
let inner = match value {
Value::Object(mut map) if map.contains_key("t") && map.contains_key("v") => {
map.remove("v").unwrap_or(Value::Null)
}
other => other,
};
let n = match &inner {
Value::Number(n) => n
.as_u64()
.or_else(|| n.as_i64().and_then(|i| u64::try_from(i).ok()))
.unwrap_or(0),
Value::String(s) => s.parse().unwrap_or(0),
_ => 0,
};
Ok(VariantU64(n))
}
}
#[cfg(test)]
mod tests {
use super::{Variant, VariantU64};
use serde::Deserialize;
use serde_json::json;
#[test]
fn unwraps_string_variant_envelope() {
let v: Variant<String> = serde_json::from_value(json!({"t": "s", "v": "active"})).unwrap();
assert_eq!(v.into_inner(), "active");
}
#[test]
fn passes_through_flat_string() {
let v: Variant<String> = serde_json::from_value(json!("active")).unwrap();
assert_eq!(v.0, "active");
}
#[test]
fn unwraps_bool_and_u64_envelopes() {
let b: Variant<bool> = serde_json::from_value(json!({"t": "b", "v": true})).unwrap();
assert!(b.0);
let n: Variant<u64> = serde_json::from_value(json!({"t": "t", "v": 42})).unwrap();
assert_eq!(n.0, 42);
}
#[test]
fn passes_through_flat_bool_and_u64() {
let b: Variant<bool> = serde_json::from_value(json!(false)).unwrap();
assert!(!b.0);
let n: Variant<u64> = serde_json::from_value(json!(7)).unwrap();
assert_eq!(n.0, 7);
}
#[test]
fn object_without_type_tag_is_not_unwrapped() {
#[derive(Debug, Deserialize, PartialEq)]
struct Inner {
v: u64,
}
let v: Variant<Inner> = serde_json::from_value(json!({"v": 9})).unwrap();
assert_eq!(v.0, Inner { v: 9 });
}
#[test]
fn variant_u64_from_number() {
let v: VariantU64 = serde_json::from_value(json!(42)).unwrap();
assert_eq!(v.0, 42);
}
#[test]
fn variant_u64_from_string() {
let v: VariantU64 = serde_json::from_value(json!("12345")).unwrap();
assert_eq!(v.0, 12345);
}
#[test]
fn variant_u64_from_wrapped_number() {
let v: VariantU64 = serde_json::from_value(json!({"t": "t", "v": 99})).unwrap();
assert_eq!(v.0, 99);
}
#[test]
fn variant_u64_from_wrapped_string() {
let v: VariantU64 = serde_json::from_value(json!({"t": "t", "v": "456"})).unwrap();
assert_eq!(v.0, 456);
}
#[test]
fn variant_u64_defaults_on_bad_input() {
let v: VariantU64 = serde_json::from_value(json!(null)).unwrap();
assert_eq!(v.0, 0);
let v: VariantU64 = serde_json::from_value(json!("not_a_number")).unwrap();
assert_eq!(v.0, 0);
}
#[test]
fn variant_u64_handles_negative_i64() {
let v: VariantU64 = serde_json::from_value(json!(-5)).unwrap();
assert_eq!(v.0, 0);
}
#[test]
fn variant_u64_defaults_when_absent_in_struct() {
#[derive(Debug, Deserialize, Default)]
struct Props {
#[serde(default)]
size: VariantU64,
}
let p: Props = serde_json::from_value(json!({})).unwrap();
assert_eq!(p.size.0, 0);
}
#[test]
fn wrong_inner_type_is_a_decode_error() {
let err = serde_json::from_value::<Variant<String>>(json!({"t": "i", "v": 5}));
assert!(err.is_err());
}
#[test]
fn fields_default_when_absent_in_struct() {
#[derive(Debug, Deserialize, Default)]
struct Props {
#[serde(default)]
id: Variant<String>,
}
let p: Props = serde_json::from_value(json!({})).unwrap();
assert_eq!(p.id.0, "");
}
}