dpp/serialization/json/
safe_integer_map.rs

1//! Serde `with` modules for `BTreeMap` fields containing u64 keys and/or values.
2//!
3//! Unlike bare u64 fields (handled automatically by `#[json_safe_fields]`), map
4//! fields need explicit `#[serde(with = "...")]` annotations because the macro
5//! cannot inject `serde(with)` into generic container internals.
6//!
7//! ## When to use
8//!
9//! If a `#[json_safe_fields]`-annotated struct has a `BTreeMap<K, u64>` field,
10//! the compile-time `JsonSafeFields` check will fail (u64 doesn't implement the
11//! trait). Add one of these modules as a `serde(with)` annotation to fix it.
12//!
13//! ## Available modules
14//!
15//! - [`json_safe_u64_u64_map`] — `BTreeMap<u64, u64>`
16//! - [`json_safe_identifier_u64_map`] — `BTreeMap<Identifier, u64>`
17//! - [`json_safe_generic_u64_value_map`] — `BTreeMap<K, u64>` for any serializable key
18//! - [`json_safe_u64_nested_identifier_u64_map`] — `BTreeMap<u64, BTreeMap<Identifier, u64>>`
19//!
20//! ## Behavior
21//!
22//! Same as `safe_integer.rs`: uses `is_human_readable()` to only stringify in
23//! JSON mode. platform_value and bincode stay native.
24
25use super::safe_integer::JS_MAX_SAFE_INTEGER;
26
27/// Serde `with` module for `BTreeMap<u64, u64>` fields.
28///
29/// - Keys: JSON map keys are always strings, so keys are inherently safe.
30///   Deserialization accepts both string and numeric keys.
31/// - Values: Large u64 values are serialized as strings in JSON.
32/// - Non-HR (platform_value): native u64 for both keys and values.
33pub mod json_safe_u64_u64_map {
34    use serde::de::{self, Deserializer, MapAccess, Visitor};
35    use serde::ser::{SerializeMap, Serializer};
36    use std::collections::BTreeMap;
37
38    use super::JS_MAX_SAFE_INTEGER;
39
40    pub fn serialize<S: Serializer>(
41        map: &BTreeMap<u64, u64>,
42        serializer: S,
43    ) -> Result<S::Ok, S::Error> {
44        if serializer.is_human_readable() {
45            let mut s = serializer.serialize_map(Some(map.len()))?;
46            for (k, v) in map {
47                // JSON keys are always strings
48                s.serialize_entry(
49                    &k.to_string(),
50                    &if *v > JS_MAX_SAFE_INTEGER {
51                        serde_json::Value::String(v.to_string())
52                    } else {
53                        serde_json::Value::Number((*v).into())
54                    },
55                )?;
56            }
57            s.end()
58        } else {
59            serde::Serialize::serialize(map, serializer)
60        }
61    }
62
63    pub fn deserialize<'de, D: Deserializer<'de>>(
64        deserializer: D,
65    ) -> Result<BTreeMap<u64, u64>, D::Error> {
66        if deserializer.is_human_readable() {
67            deserializer.deserialize_map(U64U64MapVisitor)
68        } else {
69            serde::Deserialize::deserialize(deserializer)
70        }
71    }
72
73    struct U64U64MapVisitor;
74
75    impl<'de> Visitor<'de> for U64U64MapVisitor {
76        type Value = BTreeMap<u64, u64>;
77
78        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
79            f.write_str("a map with u64 keys and u64 values (numbers or strings)")
80        }
81
82        fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
83            let mut map = BTreeMap::new();
84            while let Some((key, value)) =
85                access.next_entry::<serde_json::Value, serde_json::Value>()?
86            {
87                let k = json_value_to_u64(&key).map_err(de::Error::custom)?;
88                let v = json_value_to_u64(&value).map_err(de::Error::custom)?;
89                map.insert(k, v);
90            }
91            Ok(map)
92        }
93    }
94
95    fn json_value_to_u64(v: &serde_json::Value) -> Result<u64, String> {
96        match v {
97            serde_json::Value::Number(n) => n
98                .as_u64()
99                .ok_or_else(|| format!("expected u64 number, got: {n}")),
100            serde_json::Value::String(s) => s
101                .parse::<u64>()
102                .map_err(|_| format!("invalid u64 string: {s}")),
103            other => Err(format!("expected u64 or string, got: {other}")),
104        }
105    }
106}
107
108/// Serde `with` module for `BTreeMap<Identifier, u64>` fields.
109///
110/// - Keys: Identifier (not u64, uses its own serde impl).
111/// - Values: Large u64 values are serialized as strings in JSON.
112/// - Non-HR (platform_value): native serialization.
113pub mod json_safe_identifier_u64_map {
114    use platform_value::Identifier;
115    use serde::de::{self, Deserializer, MapAccess, Visitor};
116    use serde::ser::{SerializeMap, Serializer};
117    use std::collections::BTreeMap;
118
119    use super::JS_MAX_SAFE_INTEGER;
120
121    pub fn serialize<S: Serializer>(
122        map: &BTreeMap<Identifier, u64>,
123        serializer: S,
124    ) -> Result<S::Ok, S::Error> {
125        if serializer.is_human_readable() {
126            let mut s = serializer.serialize_map(Some(map.len()))?;
127            for (k, v) in map {
128                s.serialize_entry(
129                    k,
130                    &if *v > JS_MAX_SAFE_INTEGER {
131                        serde_json::Value::String(v.to_string())
132                    } else {
133                        serde_json::Value::Number((*v).into())
134                    },
135                )?;
136            }
137            s.end()
138        } else {
139            serde::Serialize::serialize(map, serializer)
140        }
141    }
142
143    pub fn deserialize<'de, D: Deserializer<'de>>(
144        deserializer: D,
145    ) -> Result<BTreeMap<Identifier, u64>, D::Error> {
146        if deserializer.is_human_readable() {
147            deserializer.deserialize_map(IdentifierU64MapVisitor)
148        } else {
149            serde::Deserialize::deserialize(deserializer)
150        }
151    }
152
153    struct IdentifierU64MapVisitor;
154
155    impl<'de> Visitor<'de> for IdentifierU64MapVisitor {
156        type Value = BTreeMap<Identifier, u64>;
157
158        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
159            f.write_str("a map with Identifier keys and u64 values (numbers or strings)")
160        }
161
162        fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
163            let mut map = BTreeMap::new();
164            while let Some((key, value)) = access.next_entry::<Identifier, serde_json::Value>()? {
165                let v = match &value {
166                    serde_json::Value::Number(n) => n
167                        .as_u64()
168                        .ok_or_else(|| de::Error::custom(format!("expected u64 number, got: {n}"))),
169                    serde_json::Value::String(s) => s
170                        .parse::<u64>()
171                        .map_err(|_| de::Error::custom(format!("invalid u64 string: {s}"))),
172                    other => Err(de::Error::custom(format!(
173                        "expected u64 or string, got: {other}"
174                    ))),
175                }?;
176                map.insert(key, v);
177            }
178            Ok(map)
179        }
180    }
181}
182
183/// Serde `with` module for `BTreeMap<u64, BTreeMap<Identifier, u64>>` fields.
184///
185/// - Outer keys: u64 (JSON keys always strings, accept both).
186/// - Inner keys: Identifier.
187/// - Inner values: u64, stringified when large.
188/// - Non-HR (platform_value): native serialization.
189pub mod json_safe_u64_nested_identifier_u64_map {
190    use platform_value::Identifier;
191    use serde::de::{self, Deserializer, MapAccess, Visitor};
192    use serde::ser::{SerializeMap, Serializer};
193    use std::collections::BTreeMap;
194
195    use super::JS_MAX_SAFE_INTEGER;
196
197    pub fn serialize<S: Serializer>(
198        map: &BTreeMap<u64, BTreeMap<Identifier, u64>>,
199        serializer: S,
200    ) -> Result<S::Ok, S::Error> {
201        if serializer.is_human_readable() {
202            let mut outer = serializer.serialize_map(Some(map.len()))?;
203            for (k, inner) in map {
204                let safe_inner: BTreeMap<&Identifier, serde_json::Value> = inner
205                    .iter()
206                    .map(|(ik, iv)| {
207                        let v = if *iv > JS_MAX_SAFE_INTEGER {
208                            serde_json::Value::String(iv.to_string())
209                        } else {
210                            serde_json::Value::Number((*iv).into())
211                        };
212                        (ik, v)
213                    })
214                    .collect();
215                outer.serialize_entry(&k.to_string(), &safe_inner)?;
216            }
217            outer.end()
218        } else {
219            serde::Serialize::serialize(map, serializer)
220        }
221    }
222
223    pub fn deserialize<'de, D: Deserializer<'de>>(
224        deserializer: D,
225    ) -> Result<BTreeMap<u64, BTreeMap<Identifier, u64>>, D::Error> {
226        if deserializer.is_human_readable() {
227            deserializer.deserialize_map(OuterMapVisitor)
228        } else {
229            serde::Deserialize::deserialize(deserializer)
230        }
231    }
232
233    struct OuterMapVisitor;
234
235    impl<'de> Visitor<'de> for OuterMapVisitor {
236        type Value = BTreeMap<u64, BTreeMap<Identifier, u64>>;
237
238        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
239            f.write_str("a nested map: u64 -> (Identifier -> u64)")
240        }
241
242        fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
243            let mut map = BTreeMap::new();
244            while let Some((key_str, inner_json)) =
245                access.next_entry::<serde_json::Value, BTreeMap<Identifier, serde_json::Value>>()?
246            {
247                let k = match &key_str {
248                    serde_json::Value::Number(n) => n
249                        .as_u64()
250                        .ok_or_else(|| de::Error::custom(format!("expected u64 key, got: {n}"))),
251                    serde_json::Value::String(s) => s
252                        .parse::<u64>()
253                        .map_err(|_| de::Error::custom(format!("invalid u64 key: {s}"))),
254                    other => Err(de::Error::custom(format!("expected u64 key, got: {other}"))),
255                }?;
256
257                let inner: BTreeMap<Identifier, u64> = inner_json
258                    .into_iter()
259                    .map(|(ik, iv)| {
260                        let v = match &iv {
261                            serde_json::Value::Number(n) => n.as_u64().ok_or_else(|| {
262                                de::Error::custom(format!("expected u64 value, got: {n}"))
263                            }),
264                            serde_json::Value::String(s) => s
265                                .parse::<u64>()
266                                .map_err(|_| de::Error::custom(format!("invalid u64 string: {s}"))),
267                            other => Err(de::Error::custom(format!(
268                                "expected u64 or string, got: {other}"
269                            ))),
270                        }?;
271                        Ok((ik, v))
272                    })
273                    .collect::<Result<_, M::Error>>()?;
274
275                map.insert(k, inner);
276            }
277            Ok(map)
278        }
279    }
280}
281
282/// Generic serde `with` module for `BTreeMap<K, u64>` fields where K is any
283/// serializable/deserializable key type.
284///
285/// - Keys: Use their own serde impl (unchanged).
286/// - Values: Large u64 values are serialized as strings in JSON.
287/// - Non-HR (platform_value): native serialization.
288pub mod json_safe_generic_u64_value_map {
289    use serde::de::{self, Deserializer, MapAccess, Visitor};
290    use serde::ser::{SerializeMap, Serializer};
291    use std::collections::BTreeMap;
292    use std::marker::PhantomData;
293
294    use super::JS_MAX_SAFE_INTEGER;
295
296    pub fn serialize<K, S>(map: &BTreeMap<K, u64>, serializer: S) -> Result<S::Ok, S::Error>
297    where
298        K: serde::Serialize + Ord,
299        S: Serializer,
300    {
301        if serializer.is_human_readable() {
302            let mut s = serializer.serialize_map(Some(map.len()))?;
303            for (k, v) in map {
304                s.serialize_entry(
305                    k,
306                    &if *v > JS_MAX_SAFE_INTEGER {
307                        serde_json::Value::String(v.to_string())
308                    } else {
309                        serde_json::Value::Number((*v).into())
310                    },
311                )?;
312            }
313            s.end()
314        } else {
315            serde::Serialize::serialize(map, serializer)
316        }
317    }
318
319    pub fn deserialize<'de, K, D>(deserializer: D) -> Result<BTreeMap<K, u64>, D::Error>
320    where
321        K: serde::Deserialize<'de> + Ord,
322        D: Deserializer<'de>,
323    {
324        if deserializer.is_human_readable() {
325            deserializer.deserialize_map(GenericU64ValueMapVisitor(PhantomData))
326        } else {
327            serde::Deserialize::deserialize(deserializer)
328        }
329    }
330
331    struct GenericU64ValueMapVisitor<K>(PhantomData<K>);
332
333    impl<'de, K> Visitor<'de> for GenericU64ValueMapVisitor<K>
334    where
335        K: serde::Deserialize<'de> + Ord,
336    {
337        type Value = BTreeMap<K, u64>;
338
339        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
340            f.write_str("a map with u64 values (numbers or strings)")
341        }
342
343        fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
344            let mut map = BTreeMap::new();
345            while let Some((key, value)) = access.next_entry::<K, serde_json::Value>()? {
346                let v = match &value {
347                    serde_json::Value::Number(n) => n
348                        .as_u64()
349                        .ok_or_else(|| de::Error::custom(format!("expected u64 number, got: {n}"))),
350                    serde_json::Value::String(s) => s
351                        .parse::<u64>()
352                        .map_err(|_| de::Error::custom(format!("invalid u64 string: {s}"))),
353                    other => Err(de::Error::custom(format!(
354                        "expected u64 or string, got: {other}"
355                    ))),
356                }?;
357                map.insert(key, v);
358            }
359            Ok(map)
360        }
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367    use platform_value::string_encoding::Encoding;
368    use platform_value::Identifier;
369    use serde::{Deserialize, Serialize};
370    use std::collections::BTreeMap;
371
372    #[derive(Debug, PartialEq, Serialize, Deserialize)]
373    struct TestIdentifierU64Map {
374        #[serde(with = "json_safe_identifier_u64_map")]
375        data: BTreeMap<Identifier, u64>,
376    }
377
378    #[derive(Debug, PartialEq, Serialize, Deserialize)]
379    struct TestU64U64Map {
380        #[serde(with = "json_safe_u64_u64_map")]
381        data: BTreeMap<u64, u64>,
382    }
383
384    #[derive(Debug, PartialEq, Serialize, Deserialize)]
385    struct TestNestedMap {
386        #[serde(with = "json_safe_u64_nested_identifier_u64_map")]
387        data: BTreeMap<u64, BTreeMap<Identifier, u64>>,
388    }
389
390    #[test]
391    fn identifier_u64_map_small_values_stay_numbers() {
392        let id = Identifier::random();
393        let mut data = BTreeMap::new();
394        data.insert(id, 42u64);
395        let t = TestIdentifierU64Map { data };
396        let json = serde_json::to_value(&t).unwrap();
397        let restored: TestIdentifierU64Map = serde_json::from_value(json).unwrap();
398        assert_eq!(t, restored);
399    }
400
401    #[test]
402    fn identifier_u64_map_large_values_become_strings() {
403        let id = Identifier::random();
404        let mut data = BTreeMap::new();
405        data.insert(id, u64::MAX);
406        let t = TestIdentifierU64Map { data };
407        let json = serde_json::to_value(&t).unwrap();
408
409        // The value should be a string
410        let map_obj = json["data"].as_object().unwrap();
411        let val = map_obj.values().next().unwrap();
412        assert!(val.is_string());
413        assert_eq!(val.as_str().unwrap(), "18446744073709551615");
414
415        let restored: TestIdentifierU64Map = serde_json::from_value(json).unwrap();
416        assert_eq!(t, restored);
417    }
418
419    #[test]
420    fn u64_u64_map_round_trip_with_large_values() {
421        let mut data = BTreeMap::new();
422        data.insert(100u64, 42u64);
423        data.insert(u64::MAX, u64::MAX);
424        let t = TestU64U64Map { data };
425        let json = serde_json::to_value(&t).unwrap();
426
427        // Large value should be stringified
428        let map_obj = json["data"].as_object().unwrap();
429        let large_val = &map_obj["18446744073709551615"];
430        assert!(large_val.is_string());
431
432        let restored: TestU64U64Map = serde_json::from_value(json).unwrap();
433        assert_eq!(t, restored);
434    }
435
436    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
437    struct CustomKey(String);
438
439    #[derive(Debug, PartialEq, Serialize, Deserialize)]
440    struct TestGenericMap {
441        #[serde(with = "json_safe_generic_u64_value_map")]
442        data: BTreeMap<CustomKey, u64>,
443    }
444
445    #[test]
446    fn generic_map_small_values_stay_numbers() {
447        let mut data = BTreeMap::new();
448        data.insert(CustomKey("alice".into()), 42u64);
449        let t = TestGenericMap { data };
450        let json = serde_json::to_value(&t).unwrap();
451
452        let val = &json["data"]["alice"];
453        assert!(val.is_number());
454        assert_eq!(val.as_u64().unwrap(), 42);
455
456        let restored: TestGenericMap = serde_json::from_value(json).unwrap();
457        assert_eq!(t, restored);
458    }
459
460    #[test]
461    fn generic_map_large_values_become_strings() {
462        let mut data = BTreeMap::new();
463        data.insert(CustomKey("bob".into()), u64::MAX);
464        let t = TestGenericMap { data };
465        let json = serde_json::to_value(&t).unwrap();
466
467        let val = &json["data"]["bob"];
468        assert!(val.is_string());
469        assert_eq!(val.as_str().unwrap(), "18446744073709551615");
470
471        let restored: TestGenericMap = serde_json::from_value(json).unwrap();
472        assert_eq!(t, restored);
473    }
474
475    #[test]
476    fn generic_map_mixed_values_round_trip() {
477        let mut data = BTreeMap::new();
478        data.insert(CustomKey("small".into()), 100u64);
479        data.insert(CustomKey("large".into()), u64::MAX);
480        let t = TestGenericMap { data };
481        let json = serde_json::to_value(&t).unwrap();
482
483        // Small stays number, large becomes string
484        assert!(json["data"]["small"].is_number());
485        assert!(json["data"]["large"].is_string());
486
487        let restored: TestGenericMap = serde_json::from_value(json).unwrap();
488        assert_eq!(t, restored);
489    }
490
491    #[test]
492    fn u64_u64_map_small_values_stay_numbers() {
493        let mut data = BTreeMap::new();
494        data.insert(5u64, 10u64);
495        let t = TestU64U64Map { data };
496        let json = serde_json::to_value(&t).unwrap();
497        let map_obj = json["data"].as_object().unwrap();
498        let val = &map_obj["5"];
499        assert!(val.is_number());
500
501        let restored: TestU64U64Map = serde_json::from_value(json).unwrap();
502        assert_eq!(t, restored);
503    }
504
505    #[test]
506    fn u64_u64_map_empty_round_trip() {
507        let t = TestU64U64Map {
508            data: BTreeMap::new(),
509        };
510        let json = serde_json::to_value(&t).unwrap();
511        let restored: TestU64U64Map = serde_json::from_value(json).unwrap();
512        assert_eq!(t, restored);
513    }
514
515    #[test]
516    fn identifier_u64_map_empty_round_trip() {
517        let t = TestIdentifierU64Map {
518            data: BTreeMap::new(),
519        };
520        let json = serde_json::to_value(&t).unwrap();
521        let restored: TestIdentifierU64Map = serde_json::from_value(json).unwrap();
522        assert_eq!(t, restored);
523    }
524
525    #[test]
526    fn platform_value_generic_map_round_trip() {
527        let mut data = BTreeMap::new();
528        data.insert(CustomKey("x".into()), u64::MAX);
529        let t = TestGenericMap { data };
530        let pv = platform_value::to_value(&t).unwrap();
531        let restored: TestGenericMap = platform_value::from_value(pv).unwrap();
532        assert_eq!(t, restored);
533    }
534
535    #[test]
536    fn u64_u64_map_invalid_value_type_fails() {
537        let json = serde_json::json!({"data": {"1": true}});
538        let result = serde_json::from_value::<TestU64U64Map>(json);
539        assert!(result.is_err());
540    }
541
542    #[test]
543    fn u64_u64_map_invalid_value_string_fails() {
544        let json = serde_json::json!({"data": {"1": "not_a_number"}});
545        let result = serde_json::from_value::<TestU64U64Map>(json);
546        assert!(result.is_err());
547    }
548
549    #[test]
550    fn identifier_u64_map_invalid_value_type_fails() {
551        let id = Identifier::random();
552        let json = serde_json::json!({"data": {id.to_string(Encoding::Base58): [1, 2, 3]}});
553        let result = serde_json::from_value::<TestIdentifierU64Map>(json);
554        assert!(result.is_err());
555    }
556
557    #[test]
558    fn nested_map_empty_inner_round_trip() {
559        let mut data = BTreeMap::new();
560        data.insert(1u64, BTreeMap::new());
561        let t = TestNestedMap { data };
562        let json = serde_json::to_value(&t).unwrap();
563        let restored: TestNestedMap = serde_json::from_value(json).unwrap();
564        assert_eq!(t, restored);
565    }
566
567    #[test]
568    fn nested_map_round_trip() {
569        let id = Identifier::random();
570        let mut inner = BTreeMap::new();
571        inner.insert(id, u64::MAX);
572        let mut data = BTreeMap::new();
573        data.insert(1735689600000u64, inner);
574        let t = TestNestedMap { data };
575        let json = serde_json::to_value(&t).unwrap();
576        let restored: TestNestedMap = serde_json::from_value(json).unwrap();
577        assert_eq!(t, restored);
578    }
579}