use std::{collections::HashMap, marker::PhantomData};
use serde::{de::DeserializeOwned, ser::SerializeMap, Deserializer, Serialize, Serializer};
pub trait NumberedItems: Default {
type Item: DeserializeOwned + Serialize;
const PREFIX: &'static str;
fn key_matches(name: &str) -> bool {
Self::check_key(name).is_some()
}
fn check_key(name: &str) -> Option<u32> {
let (first_part, second_part) = name.split_once(Self::PREFIX)?;
if first_part.is_empty() {
let num = u32::from_str_radix(second_part, 10).ok()?;
Some(num)
} else {
None
}
}
fn make_key(number: &u32) -> String {
format!("{}{}", Self::PREFIX, number)
}
}
pub trait Test {
fn test_fn() -> fn(&str) -> bool;
}
pub fn deserialize_additional_data<'de, T, O, D>(d: D) -> Result<HashMap<String, O>, D::Error>
where
D: Deserializer<'de>,
T: Test,
O: serde::de::Deserialize<'de>,
{
struct Visitor<O, T> {
_phantom: PhantomData<(O, T)>,
}
impl<O, T> Default for Visitor<O, T> {
fn default() -> Self {
Self {
_phantom: Default::default(),
}
}
}
impl<'de, O, T> serde::de::Visitor<'de> for Visitor<O, T>
where
O: serde::de::Deserialize<'de>,
T: Test,
{
type Value = HashMap<String, O>;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "additional properties")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut output = HashMap::new();
loop {
let (key, value) = match map.next_entry::<&str, O>() {
Ok(Some(key)) => key,
Ok(None) => break,
Err(_) => continue,
};
if (T::test_fn())(key) {
continue;
}
output.insert(key.to_string(), value);
}
Ok(output)
}
}
d.deserialize_map(Visitor::<O, T>::default())
}
pub fn serialize_multi<V, S>(value: &HashMap<u32, V::Item>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
V: NumberedItems,
{
let mut map = s.serialize_map(Some(value.len()))?;
for (key, value) in value.iter() {
map.serialize_entry(&V::make_key(key), value)?;
}
map.end()
}
pub fn deserialize_multi<'de, V, D>(d: D) -> Result<HashMap<u32, V::Item>, D::Error>
where
V: NumberedItems,
D: Deserializer<'de>,
{
struct InternalVisitor<V, D> {
_phantom: PhantomData<(D, V)>,
}
impl<V, D> Default for InternalVisitor<V, D> {
fn default() -> Self {
Self {
_phantom: Default::default(),
}
}
}
impl<'de, V, D> serde::de::Visitor<'de> for InternalVisitor<V, D>
where
V: NumberedItems,
D: Deserializer<'de>,
{
type Value = HashMap<u32, V::Item>;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Multi-numbered items with prefix {}", V::PREFIX)
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut output = HashMap::new();
while let Some(key) = map.next_key::<&str>()? {
let key = if let Some(idx) = V::check_key(key) {
idx
} else {
continue;
};
let value = map.next_value::<V::Item>()?;
output.insert(key, value);
}
Ok(output)
}
}
d.deserialize_map(InternalVisitor::<V, D>::default())
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::{NumberedItems, Test};
#[derive(Default)]
struct NumberedNames;
impl NumberedItems for NumberedNames {
type Item = String;
const PREFIX: &'static str = "name";
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct Names {
#[serde(
flatten,
deserialize_with = "super::deserialize_multi::<'_, NumberedNames, _>",
serialize_with = "super::serialize_multi::<NumberedNames, _>"
)]
names: HashMap<u32, String>,
#[serde(
flatten,
deserialize_with = "super::deserialize_additional_data::<'_, Names, _, _>"
)]
additional_data: HashMap<String, String>,
}
impl Test for Names {
fn test_fn() -> fn(&str) -> bool {
fn the_test(input: &str) -> bool {
["names"].contains(&input) || NumberedNames::key_matches(input)
}
the_test
}
}
fn map_kv<K, V, NK, NV>((k, v): (K, V)) -> (NK, NV)
where
K: Into<NK>,
V: Into<NV>,
{
(k.into(), v.into())
}
#[test]
pub fn deserialize_multi() {
let text = r#"{ "name1": "value1", "name2": "value2", "name_4": 0, "name_3": false, "name_2": "additional" }"#;
let names: Names = serde_json::from_str(text).unwrap();
let expected = Names {
names: [(1u32, "value1"), (2, "value2")]
.into_iter()
.map(map_kv)
.collect(),
additional_data: [("name_2", "additional")].into_iter().map(map_kv).collect(),
};
assert_eq!(names, expected);
}
#[test]
fn serialize_mluti() {
let names = Names {
names: [(1u32, "value1"), (2, "value2")]
.into_iter()
.map(map_kv)
.collect(),
additional_data: [("name_2", "additional")].into_iter().map(map_kv).collect(),
};
let expected = r#"{"name1":"value1","name2":"value2","name_2":"additional"}"#;
let json = serde_json::to_string(&names).unwrap();
assert_eq!(json, expected);
}
}