use serde::de::{self, DeserializeOwned, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::marker::PhantomData;
struct IndexKey(Option<u64>);
impl<'de> Deserialize<'de> for IndexKey {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct IndexKeyVisitor;
impl Visitor<'_> for IndexKeyVisitor {
type Value = IndexKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a number or a string holding one")
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<IndexKey, E> {
Ok(IndexKey(Some(value)))
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<IndexKey, E> {
Ok(IndexKey(u64::try_from(value).ok()))
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<IndexKey, E> {
Ok(IndexKey(value.parse().ok()))
}
}
deserializer.deserialize_any(IndexKeyVisitor)
}
}
struct IndexMapVisitor<K, T, M>(PhantomData<(K, T, M)>);
impl<'de, K, T, M> Visitor<'de> for IndexMapVisitor<K, T, M>
where
K: TryFrom<u64>,
T: Deserialize<'de>,
M: FromIterator<(K, T)>,
{
type Value = M;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with numeric keys")
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<M, A::Error> {
let mut entries = Vec::with_capacity(map.size_hint().unwrap_or(0));
while let Some((key, value)) = map.next_entry::<IndexKey, T>()? {
if let Some(key) = key.0.and_then(|key| K::try_from(key).ok()) {
entries.push((key, value));
}
}
Ok(entries.into_iter().collect())
}
}
fn deserialize_index_map<'de, D, K, T, M>(deserializer: D) -> Result<M, D::Error>
where
D: Deserializer<'de>,
K: TryFrom<u64>,
T: Deserialize<'de>,
M: FromIterator<(K, T)>,
{
deserializer.deserialize_map(IndexMapVisitor(PhantomData))
}
pub(crate) fn deserialize_u16_index_btreemap<'de, D, T: DeserializeOwned>(
deserializer: D,
) -> Result<BTreeMap<u16, T>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_index_map(deserializer)
}
pub(crate) fn deserialize_u16_index_hashmap<'de, D, T: DeserializeOwned>(
deserializer: D,
) -> Result<HashMap<u16, T>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_index_map::<D, u16, T, HashMap<u16, T>>(deserializer)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, serde::Deserialize)]
struct Indexed {
#[serde(deserialize_with = "deserialize_u16_index_btreemap")]
map: BTreeMap<u16, String>,
}
#[test]
fn string_keys_load_and_invalid_ones_are_dropped() {
let indexed: Indexed =
serde_json::from_str(r#"{"map": {"1": "one", "x": "dropped", "70000": "too big"}}"#)
.expect("loads");
assert_eq!(indexed.map, BTreeMap::from([(1, "one".to_owned())]));
}
}