Optional serde support agb hashmap (#801)

Probably worth having serde support in agb_hashmap.

- [x] Changelog updated
This commit is contained in:
Gwilym Inzani 2024-10-30 11:33:17 +00:00 committed by GitHub
commit 0e1820e29a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 212 additions and 0 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- Optional serde support for agb-hashmap via the `serde` feature flag
### Fixed ### Fixed
- Fixed build error due to breaking change in `xmrs`. - Fixed build error due to breaking change in `xmrs`.

View file

@ -9,11 +9,14 @@ exclude = ["/benches"]
[features] [features]
allocator_api = [] allocator_api = []
serde = ["dep:serde"]
[dependencies] [dependencies]
rustc-hash = { version = "1", default-features = false } rustc-hash = { version = "1", default-features = false }
serde = { version = "1", default-features = false, optional = true }
[dev-dependencies] [dev-dependencies]
rand = { version = "0.8", default-features = false, features = ["small_rng"] } rand = { version = "0.8", default-features = false, features = ["small_rng"] }
lazy_static = "1.4" lazy_static = "1.4"
quickcheck = "1" quickcheck = "1"
serde_json = { version = "1", default-features = false, features = ["alloc"] }

View file

@ -44,6 +44,9 @@ mod allocate {
pub(crate) use core::alloc::Allocator; pub(crate) use core::alloc::Allocator;
} }
#[cfg(feature = "serde")]
mod serde;
use core::{ use core::{
borrow::Borrow, borrow::Borrow,
fmt::Debug, fmt::Debug,

198
agb-hashmap/src/serde.rs Normal file
View file

@ -0,0 +1,198 @@
use core::{hash::Hash, marker::PhantomData};
use serde::{
de::{MapAccess, SeqAccess, Visitor},
ser::SerializeMap,
Deserialize, Serialize,
};
use crate::{ClonableAllocator, HashMap, HashSet};
mod hashmap {
use super::*;
impl<K: Serialize, V: Serialize, ALLOCATOR: ClonableAllocator> Serialize
for HashMap<K, V, ALLOCATOR>
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(self.len()))?;
for (key, value) in self {
map.serialize_entry(key, value)?;
}
map.end()
}
}
impl<'de, K, V> Deserialize<'de> for HashMap<K, V>
where
K: Deserialize<'de> + Hash + Eq,
V: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(HashMapVisitor::new())
}
}
struct HashMapVisitor<K, V> {
_marker: PhantomData<fn() -> HashMap<K, V>>,
}
impl<K, V> HashMapVisitor<K, V> {
fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<'de, K, V> Visitor<'de> for HashMapVisitor<K, V>
where
K: Deserialize<'de> + Hash + Eq,
V: Deserialize<'de>,
{
type Value = HashMap<K, V>;
fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
formatter.write_str("an agb::HashMap")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(8));
while let Some((key, value)) = access.next_entry()? {
map.insert(key, value);
}
Ok(map)
}
}
}
mod hashset {
use super::*;
impl<K: Serialize, ALLOCATOR: ClonableAllocator> Serialize for HashSet<K, ALLOCATOR> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_seq(self)
}
}
struct HashSetVisitor<K> {
_marker: PhantomData<fn() -> HashSet<K>>,
}
impl<K> HashSetVisitor<K> {
fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<'de, K> Visitor<'de> for HashSetVisitor<K>
where
K: Deserialize<'de> + Hash + Eq,
{
type Value = HashSet<K>;
fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
formatter.write_str("an agb::HashSet")
}
fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut set = HashSet::with_capacity(access.size_hint().unwrap_or(8));
while let Some(value) = access.next_element()? {
set.insert(value);
}
Ok(set)
}
}
impl<'de, K> Deserialize<'de> for HashSet<K>
where
K: Deserialize<'de> + Hash + Eq,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(HashSetVisitor::new())
}
}
}
#[cfg(test)]
mod test {
use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{HashMap, HashSet};
#[test]
fn deserialize_map() {
let json = r#"
{
"three": 3,
"seven": 7
}
"#;
let map = serde_json::from_str::<HashMap<String, i32>>(json).unwrap();
assert_eq!(
map,
HashMap::from_iter([("three".to_string(), 3), ("seven".to_string(), 7)])
);
}
#[test]
fn serialize_map() {
let map = HashMap::from_iter([("three".to_string(), 3), ("seven".to_string(), 7)]);
let json = serde_json::to_string(&map).unwrap();
let possibilities = &[r#"{"three":3,"seven":7}"#, r#"{"seven":7,"three":3}"#];
assert!(possibilities.contains(&json.as_str()));
}
#[test]
fn deserialize_hashset() {
let json = "[1, 2, 5, 8, 9, 3, 4]";
let set = serde_json::from_str::<HashSet<i32>>(json).unwrap();
assert_eq!(set, HashSet::from_iter([1, 2, 3, 4, 5, 8, 9]));
}
#[test]
fn serialize_hashset() {
let set = HashSet::from_iter([1, 2, 3, 5, 8, 9, 10]);
let serialized = serde_json::to_string(&set).unwrap();
let mut deserialized = serde_json::from_str::<Vec<i32>>(&serialized).unwrap();
deserialized.sort();
assert_eq!(deserialized, &[1, 2, 3, 5, 8, 9, 10]);
}
}

View file

@ -19,6 +19,10 @@ clippy:
just _all-crates _clippy just _all-crates _clippy
test: test:
# test the workspace
cargo test
# also need to explicitly hit the serde tests in agb-hashmap
(cd agb-hashmap && cargo test --features=serde serde)
just _test-debug agb just _test-debug agb
just _test-debug tracker/agb-tracker just _test-debug tracker/agb-tracker
just _test-multiboot just _test-multiboot