Skip to main content

proof_engine/save/
serializer.rs

1//! Serialization layer for the save system.
2//!
3//! `SerializedValue` is a JSON-compatible value enum. The `Serialize` and
4//! `Deserialize` traits let any game type opt into save/load support with a
5//! few lines of code and no derive macros.
6//!
7//! Save files are JSON, so they stay human-readable. The text itself is
8//! written and parsed by `serde_json`, which handles the corners a small
9//! hand-written parser gets wrong: non-ASCII text, surrogate pairs,
10//! trailing garbage and deeply nested input.
11
12use std::any::{Any, TypeId};
13use std::collections::HashMap;
14use glam::{Vec2, Vec3};
15
16// ─────────────────────────────────────────────
17//  DeserializeError
18// ─────────────────────────────────────────────
19
20/// Errors that can occur during deserialization.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum DeserializeError {
23    /// The value was the wrong variant (e.g. expected Int, got Str).
24    WrongType { expected: &'static str, got: &'static str },
25    /// A required key was missing from a Map.
26    MissingKey(String),
27    /// A list index was out of bounds.
28    IndexOutOfBounds { index: usize, len: usize },
29    /// The string could not be parsed as the target type.
30    ParseError(String),
31    /// A catch-all for structural/logic errors.
32    Custom(String),
33}
34
35impl std::fmt::Display for DeserializeError {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            DeserializeError::WrongType { expected, got } => {
39                write!(f, "type mismatch: expected {expected}, got {got}")
40            }
41            DeserializeError::MissingKey(k) => write!(f, "missing key: '{k}'"),
42            DeserializeError::IndexOutOfBounds { index, len } => {
43                write!(f, "index {index} out of bounds (len {len})")
44            }
45            DeserializeError::ParseError(s) => write!(f, "parse error: {s}"),
46            DeserializeError::Custom(s) => write!(f, "{s}"),
47        }
48    }
49}
50
51impl std::error::Error for DeserializeError {}
52
53// ─────────────────────────────────────────────
54//  SerializedValue
55// ─────────────────────────────────────────────
56
57/// A generic serialized value, analogous to `serde_json::Value`.
58#[derive(Debug, Clone, PartialEq)]
59pub enum SerializedValue {
60    Null,
61    Bool(bool),
62    Int(i64),
63    Float(f64),
64    Str(String),
65    Bytes(Vec<u8>),
66    List(Vec<SerializedValue>),
67    Map(HashMap<String, SerializedValue>),
68}
69
70impl SerializedValue {
71    // ── Constructors ───────────────────────────────────────────────────────
72
73    pub fn null() -> Self { SerializedValue::Null }
74    pub fn bool(b: bool) -> Self { SerializedValue::Bool(b) }
75    pub fn int(i: i64) -> Self { SerializedValue::Int(i) }
76    pub fn float(f: f64) -> Self { SerializedValue::Float(f) }
77    pub fn str(s: impl Into<String>) -> Self { SerializedValue::Str(s.into()) }
78    pub fn bytes(b: Vec<u8>) -> Self { SerializedValue::Bytes(b) }
79    pub fn list(v: Vec<SerializedValue>) -> Self { SerializedValue::List(v) }
80    pub fn map(m: HashMap<String, SerializedValue>) -> Self { SerializedValue::Map(m) }
81
82    pub fn empty_map() -> Self {
83        SerializedValue::Map(HashMap::new())
84    }
85
86    pub fn empty_list() -> Self {
87        SerializedValue::List(Vec::new())
88    }
89
90    // ── Type accessors ─────────────────────────────────────────────────────
91
92    pub fn as_bool(&self) -> Option<bool> {
93        if let SerializedValue::Bool(b) = self { Some(*b) } else { None }
94    }
95
96    pub fn as_int(&self) -> Option<i64> {
97        match self {
98            SerializedValue::Int(i) => Some(*i),
99            SerializedValue::Float(f) => Some(*f as i64),
100            _ => None,
101        }
102    }
103
104    pub fn as_float(&self) -> Option<f64> {
105        match self {
106            SerializedValue::Float(f) => Some(*f),
107            SerializedValue::Int(i) => Some(*i as f64),
108            _ => None,
109        }
110    }
111
112    pub fn as_str(&self) -> Option<&str> {
113        if let SerializedValue::Str(s) = self { Some(s.as_str()) } else { None }
114    }
115
116    pub fn as_bytes(&self) -> Option<&[u8]> {
117        if let SerializedValue::Bytes(b) = self { Some(b.as_slice()) } else { None }
118    }
119
120    pub fn as_list(&self) -> Option<&[SerializedValue]> {
121        if let SerializedValue::List(v) = self { Some(v.as_slice()) } else { None }
122    }
123
124    pub fn as_map(&self) -> Option<&HashMap<String, SerializedValue>> {
125        if let SerializedValue::Map(m) = self { Some(m) } else { None }
126    }
127
128    pub fn as_map_mut(&mut self) -> Option<&mut HashMap<String, SerializedValue>> {
129        if let SerializedValue::Map(m) = self { Some(m) } else { None }
130    }
131
132    pub fn is_null(&self) -> bool {
133        matches!(self, SerializedValue::Null)
134    }
135
136    // ── Key / index access ─────────────────────────────────────────────────
137
138    /// Get a value from a Map by key.
139    pub fn get(&self, key: &str) -> Option<&SerializedValue> {
140        self.as_map()?.get(key)
141    }
142
143    /// Get a value from a List by index.
144    pub fn index(&self, i: usize) -> Option<&SerializedValue> {
145        self.as_list()?.get(i)
146    }
147
148    /// Insert a key-value pair into a Map. Returns `false` if `self` is not a Map.
149    pub fn insert(&mut self, key: impl Into<String>, value: SerializedValue) -> bool {
150        if let SerializedValue::Map(m) = self {
151            m.insert(key.into(), value);
152            true
153        } else {
154            false
155        }
156    }
157
158    /// Push a value into a List. Returns `false` if `self` is not a List.
159    pub fn push(&mut self, value: SerializedValue) -> bool {
160        if let SerializedValue::List(v) = self {
161            v.push(value);
162            true
163        } else {
164            false
165        }
166    }
167
168    /// The variant name as a static string (for error messages).
169    pub fn type_name(&self) -> &'static str {
170        match self {
171            SerializedValue::Null => "Null",
172            SerializedValue::Bool(_) => "Bool",
173            SerializedValue::Int(_) => "Int",
174            SerializedValue::Float(_) => "Float",
175            SerializedValue::Str(_) => "Str",
176            SerializedValue::Bytes(_) => "Bytes",
177            SerializedValue::List(_) => "List",
178            SerializedValue::Map(_) => "Map",
179        }
180    }
181
182    // ── JSON encoding ──────────────────────────────────────────────────────
183
184    /// Encode to a compact JSON string.
185    ///
186    /// Map keys come out sorted, so the same value always gives the same
187    /// text. JSON has no NaN or infinity: NaN is written as `null` and
188    /// infinities as the largest finite double, `1e+308` / `-1e+308`. Bytes are written as a lowercase
189    /// hex string.
190    pub fn to_json_string(&self) -> String {
191        serde_json::to_string(&self.to_json_value()).expect("a JSON value always serializes")
192    }
193
194    /// Convert to a `serde_json::Value`, with the rules of
195    /// [`to_json_string`](Self::to_json_string).
196    pub fn to_json_value(&self) -> serde_json::Value {
197        use serde_json::Value as J;
198        match self {
199            SerializedValue::Null => J::Null,
200            SerializedValue::Bool(b) => J::Bool(*b),
201            SerializedValue::Int(i) => J::from(*i),
202            SerializedValue::Float(f) => {
203                let f = if f.is_infinite() { f.signum() * 1e308 } else { *f };
204                // from_f64 refuses only NaN, which JSON cannot hold.
205                serde_json::Number::from_f64(f).map(J::Number).unwrap_or(J::Null)
206            }
207            SerializedValue::Str(s) => J::String(s.clone()),
208            SerializedValue::Bytes(bytes) => {
209                use std::fmt::Write;
210                let mut hex = String::with_capacity(bytes.len() * 2);
211                for b in bytes {
212                    let _ = write!(hex, "{b:02x}");
213                }
214                J::String(hex)
215            }
216            SerializedValue::List(items) => J::Array(items.iter().map(Self::to_json_value).collect()),
217            SerializedValue::Map(m) => {
218                let mut keys: Vec<&String> = m.keys().collect();
219                keys.sort();
220                J::Object(keys.into_iter().map(|k| (k.clone(), m[k].to_json_value())).collect())
221            }
222        }
223    }
224
225    // ── JSON decoding ──────────────────────────────────────────────────────
226
227    /// Parse a JSON string into a `SerializedValue`.
228    ///
229    /// Integers that fit in an `i64` become `Int`; every other number
230    /// becomes `Float`. Hex strings stay `Str`, since JSON cannot say they
231    /// were bytes; `Vec<u8>` fields read them back through their own
232    /// `Deserialize`.
233    pub fn from_json_str(s: &str) -> Result<Self, DeserializeError> {
234        let v: serde_json::Value =
235            serde_json::from_str(s).map_err(|e| DeserializeError::ParseError(e.to_string()))?;
236        Ok(Self::from_json_value(v))
237    }
238
239    /// Convert from a `serde_json::Value`.
240    pub fn from_json_value(v: serde_json::Value) -> Self {
241        use serde_json::Value as J;
242        match v {
243            J::Null => SerializedValue::Null,
244            J::Bool(b) => SerializedValue::Bool(b),
245            J::Number(n) => match n.as_i64() {
246                Some(i) => SerializedValue::Int(i),
247                None => SerializedValue::Float(n.as_f64().unwrap_or(f64::NAN)),
248            },
249            J::String(s) => SerializedValue::Str(s),
250            J::Array(items) => SerializedValue::List(items.into_iter().map(Self::from_json_value).collect()),
251            J::Object(m) => SerializedValue::Map(m.into_iter().map(|(k, v)| (k, Self::from_json_value(v))).collect()),
252        }
253    }
254}
255
256impl Default for SerializedValue {
257    fn default() -> Self {
258        SerializedValue::Null
259    }
260}
261
262// ─────────────────────────────────────────────
263//  Serialize / Deserialize traits
264// ─────────────────────────────────────────────
265
266/// Convert a value to a `SerializedValue`.
267pub trait Serialize {
268    fn serialize(&self) -> SerializedValue;
269}
270
271/// Reconstruct a value from a `SerializedValue`.
272pub trait Deserialize: Sized {
273    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError>;
274}
275
276// ── bool ───────────────────────────────────────────────────────────────────
277
278impl Serialize for bool {
279    fn serialize(&self) -> SerializedValue { SerializedValue::Bool(*self) }
280}
281
282impl Deserialize for bool {
283    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
284        v.as_bool().ok_or(DeserializeError::WrongType { expected: "Bool", got: v.type_name() })
285    }
286}
287
288// ── i32 ───────────────────────────────────────────────────────────────────
289
290impl Serialize for i32 {
291    fn serialize(&self) -> SerializedValue { SerializedValue::Int(*self as i64) }
292}
293
294impl Deserialize for i32 {
295    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
296        v.as_int().map(|i| i as i32)
297            .ok_or(DeserializeError::WrongType { expected: "Int", got: v.type_name() })
298    }
299}
300
301// ── i64 ───────────────────────────────────────────────────────────────────
302
303impl Serialize for i64 {
304    fn serialize(&self) -> SerializedValue { SerializedValue::Int(*self) }
305}
306
307impl Deserialize for i64 {
308    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
309        v.as_int().ok_or(DeserializeError::WrongType { expected: "Int", got: v.type_name() })
310    }
311}
312
313// ── u32 ───────────────────────────────────────────────────────────────────
314
315impl Serialize for u32 {
316    fn serialize(&self) -> SerializedValue { SerializedValue::Int(*self as i64) }
317}
318
319impl Deserialize for u32 {
320    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
321        v.as_int().map(|i| i as u32)
322            .ok_or(DeserializeError::WrongType { expected: "Int", got: v.type_name() })
323    }
324}
325
326// ── u64 ───────────────────────────────────────────────────────────────────
327
328impl Serialize for u64 {
329    fn serialize(&self) -> SerializedValue { SerializedValue::Int(*self as i64) }
330}
331
332impl Deserialize for u64 {
333    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
334        v.as_int().map(|i| i as u64)
335            .ok_or(DeserializeError::WrongType { expected: "Int", got: v.type_name() })
336    }
337}
338
339// ── f32 ───────────────────────────────────────────────────────────────────
340
341impl Serialize for f32 {
342    fn serialize(&self) -> SerializedValue { SerializedValue::Float(*self as f64) }
343}
344
345impl Deserialize for f32 {
346    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
347        v.as_float().map(|f| f as f32)
348            .ok_or(DeserializeError::WrongType { expected: "Float", got: v.type_name() })
349    }
350}
351
352// ── f64 ───────────────────────────────────────────────────────────────────
353
354impl Serialize for f64 {
355    fn serialize(&self) -> SerializedValue { SerializedValue::Float(*self) }
356}
357
358impl Deserialize for f64 {
359    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
360        v.as_float().ok_or(DeserializeError::WrongType { expected: "Float", got: v.type_name() })
361    }
362}
363
364// ── String ─────────────────────────────────────────────────────────────────
365
366impl Serialize for String {
367    fn serialize(&self) -> SerializedValue { SerializedValue::Str(self.clone()) }
368}
369
370impl Deserialize for String {
371    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
372        v.as_str().map(|s| s.to_string())
373            .ok_or(DeserializeError::WrongType { expected: "Str", got: v.type_name() })
374    }
375}
376
377// ── &str ──────────────────────────────────────────────────────────────────
378
379impl Serialize for &str {
380    fn serialize(&self) -> SerializedValue { SerializedValue::Str(self.to_string()) }
381}
382
383// ── Vec<T> ────────────────────────────────────────────────────────────────
384
385impl<T: Serialize> Serialize for Vec<T> {
386    fn serialize(&self) -> SerializedValue {
387        SerializedValue::List(self.iter().map(|v| v.serialize()).collect())
388    }
389}
390
391impl<T: Deserialize> Deserialize for Vec<T> {
392    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
393        let list = v.as_list()
394            .ok_or(DeserializeError::WrongType { expected: "List", got: v.type_name() })?;
395        list.iter().map(T::deserialize).collect()
396    }
397}
398
399// ── HashMap<String, V> ────────────────────────────────────────────────────
400
401impl<V: Serialize> Serialize for HashMap<String, V> {
402    fn serialize(&self) -> SerializedValue {
403        let mut m = HashMap::new();
404        for (k, v) in self {
405            m.insert(k.clone(), v.serialize());
406        }
407        SerializedValue::Map(m)
408    }
409}
410
411impl<V: Deserialize> Deserialize for HashMap<String, V> {
412    fn deserialize(sv: &SerializedValue) -> Result<Self, DeserializeError> {
413        let map = sv.as_map()
414            .ok_or(DeserializeError::WrongType { expected: "Map", got: sv.type_name() })?;
415        let mut out = HashMap::new();
416        for (k, v) in map {
417            out.insert(k.clone(), V::deserialize(v)?);
418        }
419        Ok(out)
420    }
421}
422
423// ── Option<T> ─────────────────────────────────────────────────────────────
424
425impl<T: Serialize> Serialize for Option<T> {
426    fn serialize(&self) -> SerializedValue {
427        match self {
428            Some(v) => v.serialize(),
429            None => SerializedValue::Null,
430        }
431    }
432}
433
434impl<T: Deserialize> Deserialize for Option<T> {
435    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
436        if v.is_null() {
437            Ok(None)
438        } else {
439            T::deserialize(v).map(Some)
440        }
441    }
442}
443
444// ── Vec2 (glam) ───────────────────────────────────────────────────────────
445
446impl Serialize for Vec2 {
447    fn serialize(&self) -> SerializedValue {
448        let mut m = HashMap::new();
449        m.insert("x".to_string(), SerializedValue::Float(self.x as f64));
450        m.insert("y".to_string(), SerializedValue::Float(self.y as f64));
451        SerializedValue::Map(m)
452    }
453}
454
455impl Deserialize for Vec2 {
456    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
457        let x = v.get("x")
458            .and_then(|v| v.as_float())
459            .ok_or_else(|| DeserializeError::MissingKey("x".into()))? as f32;
460        let y = v.get("y")
461            .and_then(|v| v.as_float())
462            .ok_or_else(|| DeserializeError::MissingKey("y".into()))? as f32;
463        Ok(Vec2::new(x, y))
464    }
465}
466
467// ── Vec3 (glam) ───────────────────────────────────────────────────────────
468
469impl Serialize for Vec3 {
470    fn serialize(&self) -> SerializedValue {
471        let mut m = HashMap::new();
472        m.insert("x".to_string(), SerializedValue::Float(self.x as f64));
473        m.insert("y".to_string(), SerializedValue::Float(self.y as f64));
474        m.insert("z".to_string(), SerializedValue::Float(self.z as f64));
475        SerializedValue::Map(m)
476    }
477}
478
479impl Deserialize for Vec3 {
480    fn deserialize(v: &SerializedValue) -> Result<Self, DeserializeError> {
481        let x = v.get("x").and_then(|v| v.as_float())
482            .ok_or_else(|| DeserializeError::MissingKey("x".into()))? as f32;
483        let y = v.get("y").and_then(|v| v.as_float())
484            .ok_or_else(|| DeserializeError::MissingKey("y".into()))? as f32;
485        let z = v.get("z").and_then(|v| v.as_float())
486            .ok_or_else(|| DeserializeError::MissingKey("z".into()))? as f32;
487        Ok(Vec3::new(x, y, z))
488    }
489}
490
491// ─────────────────────────────────────────────
492//  ComponentSerializer
493// ─────────────────────────────────────────────
494
495type SerializeFn = Box<dyn Fn(*const u8) -> SerializedValue + Send + Sync>;
496type DeserializeFn = Box<dyn Fn(&SerializedValue) -> Box<dyn Any + Send + Sync> + Send + Sync>;
497
498struct ComponentEntry {
499    type_id: TypeId,
500    name: String,
501    serialize: SerializeFn,
502    deserialize: DeserializeFn,
503}
504
505/// Registry mapping component type names to their serialize/deserialize functions.
506///
507/// Used by the snapshot system to serialize arbitrary component types by name.
508pub struct ComponentSerializer {
509    by_name: HashMap<String, usize>,
510    by_type: HashMap<TypeId, usize>,
511    entries: Vec<ComponentEntry>,
512}
513
514impl ComponentSerializer {
515    pub fn new() -> Self {
516        Self {
517            by_name: HashMap::new(),
518            by_type: HashMap::new(),
519            entries: Vec::new(),
520        }
521    }
522
523    /// Register a component type `T` under `name`.
524    pub fn register<T: Serialize + Deserialize + Any + Send + Sync + 'static>(
525        &mut self,
526        name: impl Into<String>,
527    ) {
528        let name = name.into();
529        let type_id = TypeId::of::<T>();
530        let idx = self.entries.len();
531        self.by_name.insert(name.clone(), idx);
532        self.by_type.insert(type_id, idx);
533        self.entries.push(ComponentEntry {
534            type_id,
535            name,
536            serialize: Box::new(|ptr| {
537                // SAFETY: caller must ensure ptr points to a valid T
538                let reference = unsafe { &*(ptr as *const T) };
539                reference.serialize()
540            }),
541            deserialize: Box::new(|v| {
542                match T::deserialize(v) {
543                    Ok(t) => Box::new(t) as Box<dyn Any + Send + Sync>,
544                    Err(_) => Box::new(()) as Box<dyn Any + Send + Sync>,
545                }
546            }),
547        });
548    }
549
550    /// Serialize a component by TypeId, given a raw pointer to its data.
551    ///
552    /// # Safety
553    /// `ptr` must point to a valid value of the type identified by `type_id`.
554    pub unsafe fn serialize_any(&self, type_id: TypeId, ptr: *const u8) -> Option<SerializedValue> {
555        let idx = self.by_type.get(&type_id)?;
556        Some((self.entries[*idx].serialize)(ptr))
557    }
558
559    /// Deserialize a component by name.
560    pub fn deserialize_any(&self, name: &str, v: &SerializedValue) -> Option<Box<dyn Any + Send + Sync>> {
561        let idx = self.by_name.get(name)?;
562        Some((self.entries[*idx].deserialize)(v))
563    }
564
565    /// Returns `true` if a type is registered under `name`.
566    pub fn has_name(&self, name: &str) -> bool {
567        self.by_name.contains_key(name)
568    }
569
570    /// All registered component names.
571    pub fn registered_names(&self) -> impl Iterator<Item = &str> {
572        self.by_name.keys().map(String::as_str)
573    }
574
575    /// Number of registered component types.
576    pub fn len(&self) -> usize {
577        self.entries.len()
578    }
579}
580
581impl Default for ComponentSerializer {
582    fn default() -> Self {
583        Self::new()
584    }
585}
586
587// ─────────────────────────────────────────────
588//  Tests
589// ─────────────────────────────────────────────
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594
595    #[test]
596    fn serialize_primitives() {
597        assert_eq!(true.serialize(), SerializedValue::Bool(true));
598        assert_eq!(42i64.serialize(), SerializedValue::Int(42));
599        assert_eq!(3.14f64.serialize(), SerializedValue::Float(3.14));
600        assert_eq!("hello".serialize(), SerializedValue::Str("hello".into()));
601    }
602
603    #[test]
604    fn deserialize_primitives() {
605        assert_eq!(bool::deserialize(&SerializedValue::Bool(false)).unwrap(), false);
606        assert_eq!(i64::deserialize(&SerializedValue::Int(7)).unwrap(), 7);
607        assert_eq!(f64::deserialize(&SerializedValue::Float(1.5)).unwrap(), 1.5);
608        assert_eq!(String::deserialize(&SerializedValue::Str("hi".into())).unwrap(), "hi");
609    }
610
611    #[test]
612    fn serialize_vec2_roundtrip() {
613        let v = Vec2::new(1.0, 2.5);
614        let sv = v.serialize();
615        let v2 = Vec2::deserialize(&sv).unwrap();
616        assert!((v.x - v2.x).abs() < 1e-5);
617        assert!((v.y - v2.y).abs() < 1e-5);
618    }
619
620    #[test]
621    fn serialize_vec3_roundtrip() {
622        let v = Vec3::new(1.0, 2.0, 3.0);
623        let sv = v.serialize();
624        let v2 = Vec3::deserialize(&sv).unwrap();
625        assert!((v - v2).length() < 1e-5);
626    }
627
628    #[test]
629    fn serialize_vec_of_ints() {
630        let v: Vec<i64> = vec![10, 20, 30];
631        let sv = v.serialize();
632        let v2: Vec<i64> = Vec::deserialize(&sv).unwrap();
633        assert_eq!(v, v2);
634    }
635
636    #[test]
637    fn serialize_option_some_none() {
638        let some: Option<i64> = Some(99);
639        let none: Option<i64> = None;
640        assert_eq!(some.serialize(), SerializedValue::Int(99));
641        assert_eq!(none.serialize(), SerializedValue::Null);
642        assert_eq!(Option::<i64>::deserialize(&SerializedValue::Null).unwrap(), None);
643        assert_eq!(Option::<i64>::deserialize(&SerializedValue::Int(5)).unwrap(), Some(5));
644    }
645
646    #[test]
647    fn json_roundtrip_simple() {
648        let original = SerializedValue::Map({
649            let mut m = HashMap::new();
650            m.insert("name".into(), SerializedValue::Str("Alice".into()));
651            m.insert("score".into(), SerializedValue::Int(1000));
652            m.insert("alive".into(), SerializedValue::Bool(true));
653            m
654        });
655        let json = original.to_json_string();
656        let parsed = SerializedValue::from_json_str(&json).unwrap();
657        assert_eq!(parsed.get("name").and_then(|v| v.as_str()), Some("Alice"));
658        assert_eq!(parsed.get("score").and_then(|v| v.as_int()), Some(1000));
659        assert_eq!(parsed.get("alive").and_then(|v| v.as_bool()), Some(true));
660    }
661
662    #[test]
663    fn json_roundtrip_nested() {
664        let sv = SerializedValue::List(vec![
665            SerializedValue::Int(1),
666            SerializedValue::Float(2.5),
667            SerializedValue::Null,
668            SerializedValue::Bool(true),
669        ]);
670        let json = sv.to_json_string();
671        let parsed = SerializedValue::from_json_str(&json).unwrap();
672        assert_eq!(parsed.index(0).and_then(|v| v.as_int()), Some(1));
673        assert_eq!(parsed.index(2).map(|v| v.is_null()), Some(true));
674    }
675
676    #[test]
677    fn json_string_escape() {
678        let sv = SerializedValue::Str("say \"hello\"\nnewline".into());
679        let json = sv.to_json_string();
680        let parsed = SerializedValue::from_json_str(&json).unwrap();
681        assert_eq!(parsed.as_str(), Some("say \"hello\"\nnewline"));
682    }
683
684    #[test]
685    fn json_keeps_non_ascii_text_intact() {
686        // The old byte-at-a-time parser turned every UTF-8 byte into its
687        // own char, so "Zoë" came back as "Zoë".
688        let name = "Zoë, 東京, \u{1F525}";
689        let sv = SerializedValue::Str(name.into());
690        let parsed = SerializedValue::from_json_str(&sv.to_json_string()).unwrap();
691        assert_eq!(parsed.as_str(), Some(name));
692        // Escaped surrogate pairs decode to the one character they encode.
693        let parsed = SerializedValue::from_json_str(r#""🔥""#).unwrap();
694        assert_eq!(parsed.as_str(), Some("\u{1F525}"));
695    }
696
697    #[test]
698    fn json_output_is_deterministic_and_handles_odd_floats() {
699        let mut m = HashMap::new();
700        for k in ["zeta", "alpha", "mid"] {
701            m.insert(k.to_string(), SerializedValue::Float(1.0));
702        }
703        m.insert("nan".into(), SerializedValue::Float(f64::NAN));
704        m.insert("inf".into(), SerializedValue::Float(f64::NEG_INFINITY));
705        m.insert("raw".into(), SerializedValue::Bytes(vec![0, 15, 255]));
706        let json = SerializedValue::Map(m).to_json_string();
707        assert_eq!(
708            json,
709            r#"{"alpha":1.0,"inf":-1e+308,"mid":1.0,"nan":null,"raw":"000fff","zeta":1.0}"#
710        );
711        // Floats stay floats through a round trip, ints stay ints.
712        let back = SerializedValue::from_json_str(&json).unwrap();
713        assert_eq!(back.get("alpha"), Some(&SerializedValue::Float(1.0)));
714        assert_eq!(SerializedValue::from_json_str("42").unwrap(), SerializedValue::Int(42));
715        assert_eq!(SerializedValue::from_json_str("1e3").unwrap(), SerializedValue::Float(1000.0));
716    }
717
718    #[test]
719    fn json_rejects_malformed_input() {
720        for bad in ["{\"a\":1} trailing", "[1,2", "{\"a\" 1}", "\"unterminated", "nul", ""] {
721            assert!(
722                matches!(SerializedValue::from_json_str(bad), Err(DeserializeError::ParseError(_))),
723                "{bad:?} should not parse"
724            );
725        }
726    }
727
728    #[test]
729    fn component_serializer_register_and_use() {
730        let mut cs = ComponentSerializer::new();
731        cs.register::<i64>("health");
732        assert!(cs.has_name("health"));
733        assert_eq!(cs.len(), 1);
734
735        let sv = SerializedValue::Int(100);
736        let boxed = cs.deserialize_any("health", &sv).unwrap();
737        let val = boxed.downcast_ref::<i64>().unwrap();
738        assert_eq!(*val, 100);
739    }
740
741    #[test]
742    fn wrong_type_error() {
743        let sv = SerializedValue::Str("not a bool".into());
744        let err = bool::deserialize(&sv).unwrap_err();
745        assert!(matches!(err, DeserializeError::WrongType { .. }));
746    }
747}