Skip to main content

structfs_serde_store/
convert.rs

1//! Checked conversion to/from a plain JSON DOM. Original token spellings and
2//! duplicate keys are already lost in a DOM; use JsonCodec on untrusted wire input.
3pub use crate::value_serde::{from_value, to_value};
4use crate::{Codec, JsonCodec};
5use structfs_core_store::{Error, Format, Value};
6
7/// Convert to plain JSON, rejecting bytes and non-finite floats.
8pub fn value_to_json(value: Value) -> Result<serde_json::Value, Error> {
9    let bytes = JsonCodec.encode(&value, &Format::JSON)?;
10    serde_json::from_slice(&bytes).map_err(|e| Error::decode(Format::JSON, e.to_string()))
11}
12/// Import an already parsed JSON DOM, preserving its exact supported numbers.
13pub fn json_to_value(json: serde_json::Value) -> Value {
14    match json {
15        serde_json::Value::Null => Value::Null,
16        serde_json::Value::Bool(v) => Value::Bool(v),
17        serde_json::Value::String(v) => Value::String(v),
18        serde_json::Value::Number(n) => {
19            if let Some(i) = n.as_i64() {
20                Value::Integer(i)
21            } else if let Some(u) = n.as_u64() {
22                Value::from(u)
23            } else {
24                Value::from(n.as_f64().expect("finite JSON number"))
25            }
26        }
27        serde_json::Value::Array(a) => Value::Array(a.into_iter().map(json_to_value).collect()),
28        serde_json::Value::Object(m) => {
29            Value::Map(m.into_iter().map(|(k, v)| (k, json_to_value(v))).collect())
30        }
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use serde::{Deserialize, Serialize};
38
39    #[derive(Debug, PartialEq, Serialize, Deserialize)]
40    struct TestStruct {
41        name: String,
42        age: u32,
43        active: bool,
44    }
45
46    #[test]
47    fn roundtrip_struct() {
48        let original = TestStruct {
49            name: "Alice".to_string(),
50            age: 30,
51            active: true,
52        };
53
54        let value = to_value(&original).unwrap();
55        let recovered: TestStruct = from_value(value).unwrap();
56
57        assert_eq!(original, recovered);
58    }
59
60    #[test]
61    fn json_to_value_numbers() {
62        let json = serde_json::json!({
63            "integer": 42,
64            "float": 2.75,
65            "negative": -100
66        });
67
68        let value = json_to_value(json);
69        match value {
70            Value::Map(map) => {
71                assert_eq!(map.get("integer"), Some(&Value::Integer(42)));
72                assert_eq!(map.get("negative"), Some(&Value::Integer(-100)));
73                // Float comparison
74                if let Some(Value::Float(f)) = map.get("float") {
75                    assert!((f - 2.75).abs() < 0.001);
76                } else {
77                    panic!("expected float");
78                }
79            }
80            _ => panic!("expected map"),
81        }
82    }
83
84    #[test]
85    fn value_to_json_arrays() {
86        let value = Value::Array(vec![
87            Value::Integer(1),
88            Value::Integer(2),
89            Value::Integer(3),
90        ]);
91
92        let json = value_to_json(value).unwrap();
93        assert_eq!(json, serde_json::json!([1, 2, 3]));
94    }
95
96    #[test]
97    fn value_to_json_null() {
98        let value = Value::Null;
99        let json = value_to_json(value).unwrap();
100        assert_eq!(json, serde_json::Value::Null);
101    }
102
103    #[test]
104    fn value_to_json_bool() {
105        assert_eq!(
106            value_to_json(Value::Bool(true)).unwrap(),
107            serde_json::Value::Bool(true)
108        );
109        assert_eq!(
110            value_to_json(Value::Bool(false)).unwrap(),
111            serde_json::Value::Bool(false)
112        );
113    }
114
115    #[test]
116    fn value_to_json_string() {
117        let value = Value::String("hello world".to_string());
118        let json = value_to_json(value).unwrap();
119        assert_eq!(json, serde_json::Value::String("hello world".to_string()));
120    }
121
122    #[test]
123    fn value_to_json_integer() {
124        let value = Value::Integer(12345);
125        let json = value_to_json(value).unwrap();
126        assert_eq!(json, serde_json::json!(12345));
127    }
128
129    #[test]
130    fn value_to_json_float() {
131        let value = Value::Float(1.23456);
132        let json = value_to_json(value).unwrap();
133        if let serde_json::Value::Number(n) = json {
134            assert!((n.as_f64().unwrap() - 1.23456).abs() < 0.00001);
135        } else {
136            panic!("expected number");
137        }
138    }
139
140    #[test]
141    fn value_to_json_rejects_lossy_values() {
142        assert!(value_to_json(Value::Float(f64::NAN)).is_err());
143        assert!(value_to_json(Value::Bytes(vec![1, 2, 3])).is_err());
144    }
145
146    #[test]
147    fn value_to_json_map() {
148        use std::collections::BTreeMap;
149        let mut map = BTreeMap::new();
150        map.insert("key".to_string(), Value::String("value".to_string()));
151        map.insert("num".to_string(), Value::Integer(42));
152
153        let json = value_to_json(Value::Map(map)).unwrap();
154        assert_eq!(json, serde_json::json!({"key": "value", "num": 42}));
155    }
156
157    #[test]
158    fn json_to_value_null() {
159        let json = serde_json::Value::Null;
160        let value = json_to_value(json);
161        assert_eq!(value, Value::Null);
162    }
163
164    #[test]
165    fn json_to_value_bool() {
166        assert_eq!(
167            json_to_value(serde_json::Value::Bool(true)),
168            Value::Bool(true)
169        );
170        assert_eq!(
171            json_to_value(serde_json::Value::Bool(false)),
172            Value::Bool(false)
173        );
174    }
175
176    #[test]
177    fn json_to_value_string() {
178        let json = serde_json::Value::String("test".to_string());
179        let value = json_to_value(json);
180        assert_eq!(value, Value::String("test".to_string()));
181    }
182
183    #[test]
184    fn json_to_value_array() {
185        let json = serde_json::json!([1, "two", true]);
186        let value = json_to_value(json);
187        match value {
188            Value::Array(arr) => {
189                assert_eq!(arr.len(), 3);
190                assert_eq!(arr[0], Value::Integer(1));
191                assert_eq!(arr[1], Value::String("two".to_string()));
192                assert_eq!(arr[2], Value::Bool(true));
193            }
194            _ => panic!("expected array"),
195        }
196    }
197
198    #[test]
199    fn json_to_value_object() {
200        let json = serde_json::json!({"a": 1, "b": "two"});
201        let value = json_to_value(json);
202        match value {
203            Value::Map(map) => {
204                assert_eq!(map.get("a"), Some(&Value::Integer(1)));
205                assert_eq!(map.get("b"), Some(&Value::String("two".to_string())));
206            }
207            _ => panic!("expected map"),
208        }
209    }
210
211    #[test]
212    fn from_value_error() {
213        // Try to deserialize a string into a struct
214        let value = Value::String("not a struct".to_string());
215        let result: Result<TestStruct, _> = from_value(value);
216        assert!(result.is_err());
217    }
218
219    #[test]
220    fn to_value_primitives() {
221        assert_eq!(to_value(&42i32).unwrap(), Value::Integer(42));
222        assert_eq!(
223            to_value(&"hello").unwrap(),
224            Value::String("hello".to_string())
225        );
226        assert_eq!(to_value(&true).unwrap(), Value::Bool(true));
227    }
228
229    #[test]
230    fn to_value_vec() {
231        let vec = vec![1, 2, 3];
232        let value = to_value(&vec).unwrap();
233        match value {
234            Value::Array(arr) => {
235                assert_eq!(arr.len(), 3);
236                assert_eq!(arr[0], Value::Integer(1));
237            }
238            _ => panic!("expected array"),
239        }
240    }
241
242    #[test]
243    fn roundtrip_nested_struct() {
244        #[derive(Debug, PartialEq, Serialize, Deserialize)]
245        struct Inner {
246            value: i32,
247        }
248
249        #[derive(Debug, PartialEq, Serialize, Deserialize)]
250        struct Outer {
251            inner: Inner,
252            items: Vec<String>,
253        }
254
255        let original = Outer {
256            inner: Inner { value: 99 },
257            items: vec!["a".to_string(), "b".to_string()],
258        };
259
260        let value = to_value(&original).unwrap();
261        let recovered: Outer = from_value(value).unwrap();
262        assert_eq!(original, recovered);
263    }
264
265    #[test]
266    fn roundtrip_option() {
267        let some_value: Option<i32> = Some(42);
268        let none_value: Option<i32> = None;
269
270        let some_converted = to_value(&some_value).unwrap();
271        let none_converted = to_value(&none_value).unwrap();
272
273        let some_recovered: Option<i32> = from_value(some_converted).unwrap();
274        let none_recovered: Option<i32> = from_value(none_converted).unwrap();
275
276        assert_eq!(some_recovered, Some(42));
277        assert_eq!(none_recovered, None);
278    }
279}