use crate::planning::semantics::{DataDefinition, DataPath, LiteralValue, ValueKind};
use crate::Error;
use indexmap::IndexMap;
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::collections::HashMap;
pub fn from_json(json: &[u8]) -> Result<HashMap<String, String>, Error> {
let map: HashMap<String, Value> = serde_json::from_slice(json)
.map_err(|e| Error::validation(format!("JSON parse error: {}", e), None, None::<String>))?;
Ok(data_values_from_map(map))
}
pub fn data_values_from_map(map: HashMap<String, Value>) -> HashMap<String, String> {
map.into_iter()
.filter(|(_, v)| !v.is_null())
.map(|(k, v)| (k, json_value_to_string(&v)))
.collect()
}
fn json_value_to_string(value: &Value) -> String {
match value {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
Value::Array(_) | Value::Object(_) => serde_json::to_string(value)
.expect("BUG: serde_json::to_string failed on a serde_json::Value"),
Value::Null => unreachable!(
"null JSON values are filtered in data_values_from_map before json_value_to_string"
),
}
}
pub fn literal_value_to_json(v: &LiteralValue) -> (Value, Option<String>) {
match &v.value {
ValueKind::Boolean(b) => (Value::Bool(*b), None),
ValueKind::Number(n) => (decimal_to_json(n), None),
ValueKind::Scale(n, unit) => (decimal_to_json(n), Some(unit.clone())),
ValueKind::Ratio(r, _) => (decimal_to_json(r), None),
ValueKind::Duration(n, unit) => (decimal_to_json(n), Some(unit.to_string())),
ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => {
(Value::String(v.display_value()), None)
}
}
}
fn decimal_to_json(d: &Decimal) -> Value {
if d.fract().is_zero() {
match i64::try_from(d.trunc()) {
Ok(n) => Value::Number(n.into()),
Err(_) => Value::String(d.to_string()),
}
} else {
let s = d.to_string();
let Ok(f) = s.parse::<f64>() else {
return Value::String(s);
};
match serde_json::Number::from_f64(f) {
Some(n) => Value::Number(n),
None => Value::String(s),
}
}
}
pub fn serialize_resolved_data_value_map<S>(
map: &IndexMap<DataPath, DataDefinition>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let entries: Vec<(&DataPath, &DataDefinition)> = map.iter().collect();
entries.serialize(serializer)
}
pub fn deserialize_resolved_data_value_map<'de, D>(
deserializer: D,
) -> Result<IndexMap<DataPath, DataDefinition>, D::Error>
where
D: Deserializer<'de>,
{
let entries: Vec<(DataPath, DataDefinition)> = Vec::deserialize(deserializer)?;
Ok(entries.into_iter().collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_json_string_to_string() {
let json = br#"{"name": "Alice"}"#;
let result = from_json(json).unwrap();
assert_eq!(result.get("name"), Some(&"Alice".to_string()));
}
#[test]
fn test_json_number_to_string() {
let json = br#"{"name": 42}"#;
let result = from_json(json).unwrap();
assert_eq!(result.get("name"), Some(&"42".to_string()));
}
#[test]
fn test_json_boolean_to_string() {
let json = br#"{"name": true}"#;
let result = from_json(json).unwrap();
assert_eq!(result.get("name"), Some(&"true".to_string()));
}
#[test]
fn test_json_array_to_string() {
let json = br#"{"data": [1, 2, 3]}"#;
let result = from_json(json).unwrap();
assert_eq!(result.get("data"), Some(&"[1,2,3]".to_string()));
}
#[test]
fn test_json_object_to_string() {
let json = br#"{"config": {"key": "value"}}"#;
let result = from_json(json).unwrap();
assert_eq!(
result.get("config"),
Some(&"{\"key\":\"value\"}".to_string())
);
}
#[test]
fn test_null_value_skipped() {
let json = br#"{"name": null, "age": 30}"#;
let result = from_json(json).unwrap();
assert_eq!(result.len(), 1);
assert!(!result.contains_key("name"));
assert_eq!(result.get("age"), Some(&"30".to_string()));
}
#[test]
fn test_all_null_values() {
let json = br#"{"name": null}"#;
let result = from_json(json).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_mixed_valid_types() {
let json = br#"{"name": "Test", "count": 5, "active": true, "discount": 21}"#;
let result = from_json(json).unwrap();
assert_eq!(result.len(), 4);
assert_eq!(result.get("name"), Some(&"Test".to_string()));
assert_eq!(result.get("count"), Some(&"5".to_string()));
assert_eq!(result.get("active"), Some(&"true".to_string()));
assert_eq!(result.get("discount"), Some(&"21".to_string()));
}
#[test]
fn test_invalid_json_syntax() {
let json = br#"{"name": }"#;
let result = from_json(json);
assert!(result.is_err());
let error_message = result.unwrap_err().to_string();
assert!(error_message.contains("JSON parse error"));
}
#[test]
fn test_literal_value_to_json_number() {
use crate::planning::semantics::LiteralValue;
use std::str::FromStr;
let v = LiteralValue::number(rust_decimal::Decimal::from_str("42").unwrap());
let (val, unit) = literal_value_to_json(&v);
assert!(val.is_number());
assert_eq!(val.as_i64(), Some(42));
assert!(unit.is_none());
}
#[test]
fn test_literal_value_to_json_scale() {
use crate::planning::semantics::{primitive_scale, LiteralValue};
use std::str::FromStr;
let v = LiteralValue::scale_with_type(
rust_decimal::Decimal::from_str("99.50").unwrap(),
"eur".to_string(),
primitive_scale().clone(),
);
let (val, unit) = literal_value_to_json(&v);
assert!(val.is_number());
assert_eq!(unit.as_deref(), Some("eur"));
}
#[test]
fn test_literal_value_to_json_boolean() {
use crate::planning::semantics::LiteralValue;
let (val, unit) = literal_value_to_json(&LiteralValue::from_bool(true));
assert_eq!(val.as_bool(), Some(true));
assert!(unit.is_none());
}
#[test]
fn test_decimal_to_json_out_of_i64_fallback() {
use crate::planning::semantics::LiteralValue;
use std::str::FromStr;
let huge = rust_decimal::Decimal::from_str("9223372036854775808").unwrap();
let v = LiteralValue::number(huge);
let (val, _) = literal_value_to_json(&v);
assert!(val.is_string());
assert_eq!(val.as_str(), Some("9223372036854775808"));
}
}