lemma/serialization/
json.rs1use crate::planning::semantics::{FactData, FactPath, LiteralValue, ValueKind};
2use crate::Error;
3use indexmap::IndexMap;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_json::Value;
7use std::collections::HashMap;
8
9pub fn from_json(json: &[u8]) -> Result<HashMap<String, String>, Error> {
14 let map: HashMap<String, Value> = serde_json::from_slice(json)
15 .map_err(|e| Error::validation(format!("JSON parse error: {}", e), None, None::<String>))?;
16
17 Ok(fact_values_from_map(map))
18}
19
20pub fn fact_values_from_map(map: HashMap<String, Value>) -> HashMap<String, String> {
22 map.into_iter()
23 .filter(|(_, v)| !v.is_null())
24 .map(|(k, v)| (k, json_value_to_string(&v)))
25 .collect()
26}
27
28fn json_value_to_string(value: &Value) -> String {
29 match value {
30 Value::String(s) => s.clone(),
31 Value::Number(n) => n.to_string(),
32 Value::Bool(b) => b.to_string(),
33 Value::Array(_) | Value::Object(_) => serde_json::to_string(value)
34 .expect("BUG: serde_json::to_string failed on a serde_json::Value"),
35 Value::Null => unreachable!(
36 "null JSON values are filtered in fact_values_from_map before json_value_to_string"
37 ),
38 }
39}
40
41pub fn literal_value_to_json(v: &LiteralValue) -> (Value, Option<String>) {
50 match &v.value {
51 ValueKind::Boolean(b) => (Value::Bool(*b), None),
52 ValueKind::Number(n) => (decimal_to_json(n), None),
53 ValueKind::Scale(n, unit) => (decimal_to_json(n), Some(unit.clone())),
54 ValueKind::Ratio(r, _) => (decimal_to_json(r), None),
55 ValueKind::Duration(n, unit) => (decimal_to_json(n), Some(unit.to_string())),
56 ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => {
57 (Value::String(v.display_value()), None)
58 }
59 }
60}
61
62fn decimal_to_json(d: &Decimal) -> Value {
66 if d.fract().is_zero() {
67 match i64::try_from(d.trunc()) {
68 Ok(n) => Value::Number(n.into()),
69 Err(_) => Value::String(d.to_string()),
70 }
71 } else {
72 let s = d.to_string();
73 let Ok(f) = s.parse::<f64>() else {
74 return Value::String(s);
75 };
76 match serde_json::Number::from_f64(f) {
77 Some(n) => Value::Number(n),
78 None => Value::String(s),
79 }
80 }
81}
82
83pub fn serialize_resolved_fact_value_map<S>(
89 map: &IndexMap<FactPath, FactData>,
90 serializer: S,
91) -> Result<S::Ok, S::Error>
92where
93 S: Serializer,
94{
95 let entries: Vec<(&FactPath, &FactData)> = map.iter().collect();
96 entries.serialize(serializer)
97}
98
99pub fn deserialize_resolved_fact_value_map<'de, D>(
101 deserializer: D,
102) -> Result<IndexMap<FactPath, FactData>, D::Error>
103where
104 D: Deserializer<'de>,
105{
106 let entries: Vec<(FactPath, FactData)> = Vec::deserialize(deserializer)?;
107 Ok(entries.into_iter().collect())
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_json_string_to_string() {
116 let json = br#"{"name": "Alice"}"#;
117 let result = from_json(json).unwrap();
118 assert_eq!(result.get("name"), Some(&"Alice".to_string()));
119 }
120
121 #[test]
122 fn test_json_number_to_string() {
123 let json = br#"{"name": 42}"#;
124 let result = from_json(json).unwrap();
125 assert_eq!(result.get("name"), Some(&"42".to_string()));
126 }
127
128 #[test]
129 fn test_json_boolean_to_string() {
130 let json = br#"{"name": true}"#;
131 let result = from_json(json).unwrap();
132 assert_eq!(result.get("name"), Some(&"true".to_string()));
133 }
134
135 #[test]
136 fn test_json_array_to_string() {
137 let json = br#"{"data": [1, 2, 3]}"#;
138 let result = from_json(json).unwrap();
139 assert_eq!(result.get("data"), Some(&"[1,2,3]".to_string()));
140 }
141
142 #[test]
143 fn test_json_object_to_string() {
144 let json = br#"{"config": {"key": "value"}}"#;
145 let result = from_json(json).unwrap();
146 assert_eq!(
147 result.get("config"),
148 Some(&"{\"key\":\"value\"}".to_string())
149 );
150 }
151
152 #[test]
153 fn test_null_value_skipped() {
154 let json = br#"{"name": null, "age": 30}"#;
155 let result = from_json(json).unwrap();
156 assert_eq!(result.len(), 1);
157 assert!(!result.contains_key("name"));
158 assert_eq!(result.get("age"), Some(&"30".to_string()));
159 }
160
161 #[test]
162 fn test_all_null_values() {
163 let json = br#"{"name": null}"#;
164 let result = from_json(json).unwrap();
165 assert!(result.is_empty());
166 }
167
168 #[test]
169 fn test_mixed_valid_types() {
170 let json = br#"{"name": "Test", "count": 5, "active": true, "discount": 21}"#;
171 let result = from_json(json).unwrap();
172 assert_eq!(result.len(), 4);
173 assert_eq!(result.get("name"), Some(&"Test".to_string()));
174 assert_eq!(result.get("count"), Some(&"5".to_string()));
175 assert_eq!(result.get("active"), Some(&"true".to_string()));
176 assert_eq!(result.get("discount"), Some(&"21".to_string()));
177 }
178
179 #[test]
180 fn test_invalid_json_syntax() {
181 let json = br#"{"name": }"#;
182 let result = from_json(json);
183 assert!(result.is_err());
184 let error_message = result.unwrap_err().to_string();
185 assert!(error_message.contains("JSON parse error"));
186 }
187
188 #[test]
191 fn test_literal_value_to_json_number() {
192 use crate::planning::semantics::LiteralValue;
193 use std::str::FromStr;
194 let v = LiteralValue::number(rust_decimal::Decimal::from_str("42").unwrap());
195 let (val, unit) = literal_value_to_json(&v);
196 assert!(val.is_number());
197 assert_eq!(val.as_i64(), Some(42));
198 assert!(unit.is_none());
199 }
200
201 #[test]
202 fn test_literal_value_to_json_scale() {
203 use crate::planning::semantics::{primitive_scale, LiteralValue};
204 use std::str::FromStr;
205 let v = LiteralValue::scale_with_type(
206 rust_decimal::Decimal::from_str("99.50").unwrap(),
207 "eur".to_string(),
208 primitive_scale().clone(),
209 );
210 let (val, unit) = literal_value_to_json(&v);
211 assert!(val.is_number());
212 assert_eq!(unit.as_deref(), Some("eur"));
213 }
214
215 #[test]
216 fn test_literal_value_to_json_boolean() {
217 use crate::planning::semantics::LiteralValue;
218 let (val, unit) = literal_value_to_json(&LiteralValue::from_bool(true));
219 assert_eq!(val.as_bool(), Some(true));
220 assert!(unit.is_none());
221 }
222
223 #[test]
224 fn test_decimal_to_json_out_of_i64_fallback() {
225 use crate::planning::semantics::LiteralValue;
226 use std::str::FromStr;
227 let huge = rust_decimal::Decimal::from_str("9223372036854775808").unwrap();
229 let v = LiteralValue::number(huge);
230 let (val, _) = literal_value_to_json(&v);
231 assert!(val.is_string());
232 assert_eq!(val.as_str(), Some("9223372036854775808"));
233 }
234}