use std::{collections::BTreeMap, convert::TryInto, fmt, marker::PhantomData}; use js_int::Int; use serde::{ de::{self, Deserializer, IntoDeserializer as _, MapAccess, Visitor}, ser::Serializer, Deserialize, Serialize, }; /// Serde deserialization decorator to map empty Strings to None, /// and forward non-empty Strings to the Deserialize implementation for T. /// Useful for the typical /// "A room with an X event with an absent, null, or empty Y field /// should be treated the same as a room with no such event." /// formulation in the spec. /// /// To be used like this: /// `#[serde(deserialize_with = "empty_string_as_none")]` /// Relevant serde issue: pub fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { let opt = Option::::deserialize(de)?; match opt.as_deref() { None | Some("") => Ok(None), // If T = String, like in m.room.name, the second deserialize is actually superfluous. // TODO: optimize that somehow? Some(s) => T::deserialize(s.into_deserializer()).map(Some), } } /// Serde serializiation decorator to map None to an empty String, /// and forward Somes to the Serialize implemention for T. /// /// To be used like this: /// `#[serde(serialize_with = "empty_string_as_none")]` pub fn none_as_empty_string( value: &Option, serializer: S, ) -> Result where S: Serializer, { match value { Some(x) => x.serialize(serializer), None => serializer.serialize_str(""), } } /// Take either an integer number or a string and deserialize to an integer number. /// /// To be used like this: /// `#[serde(deserialize_with = "int_or_string_to_int")]` pub fn int_or_string_to_int<'de, D>(de: D) -> Result where D: Deserializer<'de>, { struct IntOrStringVisitor; impl<'de> Visitor<'de> for IntOrStringVisitor { type Value = Int; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an integer or a string") } fn visit_i8(self, v: i8) -> Result { Ok(v.into()) } fn visit_i16(self, v: i16) -> Result { Ok(v.into()) } fn visit_i32(self, v: i32) -> Result { Ok(v.into()) } fn visit_i64(self, v: i64) -> Result { v.try_into().map_err(E::custom) } fn visit_i128(self, v: i128) -> Result { v.try_into().map_err(E::custom) } fn visit_u8(self, v: u8) -> Result { Ok(v.into()) } fn visit_u16(self, v: u16) -> Result { Ok(v.into()) } fn visit_u32(self, v: u32) -> Result { Ok(v.into()) } fn visit_u64(self, v: u64) -> Result { v.try_into().map_err(E::custom) } fn visit_u128(self, v: u128) -> Result { v.try_into().map_err(E::custom) } fn visit_str(self, v: &str) -> Result { v.parse().map_err(E::custom) } } de.deserialize_any(IntOrStringVisitor) } /// Take a BTreeMap with values of either an integer number or a string and deserialize /// those to integer numbers. /// /// To be used like this: /// `#[serde(deserialize_with = "btreemap_int_or_string_to_int_values")]` pub fn btreemap_int_or_string_to_int_values<'de, D, T>(de: D) -> Result, D::Error> where D: Deserializer<'de>, T: Deserialize<'de> + Ord, { #[repr(transparent)] struct IntWrap(Int); impl<'de> Deserialize<'de> for IntWrap { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { int_or_string_to_int(deserializer).map(IntWrap) } } struct IntMapVisitor { _phantom: PhantomData, } impl IntMapVisitor { fn new() -> Self { Self { _phantom: PhantomData } } } impl<'de, T> Visitor<'de> for IntMapVisitor where T: Deserialize<'de> + Ord, { type Value = BTreeMap; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map with integers or stings as values") } fn visit_map>(self, mut map: A) -> Result { let mut res = BTreeMap::new(); while let Some((k, IntWrap(v))) = map.next_entry()? { res.insert(k, v); } Ok(res) } } de.deserialize_map(IntMapVisitor::new()) } #[cfg(test)] mod tests { use js_int::{int, Int}; use matches::assert_matches; use serde::Deserialize; use super::int_or_string_to_int; #[test] fn int_or_string() -> Result<(), serde_json::Error> { #[derive(Debug, Deserialize)] struct Test { #[serde(deserialize_with = "int_or_string_to_int")] num: Int, } assert_matches!( serde_json::from_value::(serde_json::json!({ "num": "0" }))?, Test { num } if num == int!(0) ); Ok(()) } }