//! JSON ↔ Rhai `Dynamic` value bridge. //! //! Originally inline in `engine.rs`; moved here for v1.1.0 so future //! service modules (KV in v1.1.1, docs in v1.1.2, …) can convert //! values without `engine.rs` being the only owner of the conversions. //! Behaviour is unchanged from the pre-extraction implementation — //! `sdk_contract.rs::json_round_trip_preserves_nested_shapes` pins the //! observable round-trip. use rhai::{Dynamic, Map}; use serde_json::Value as Json; /// Convert a `serde_json::Value` into a Rhai `Dynamic` suitable for /// pushing into a script's scope. Numbers prefer the narrowest type /// (`i64` over `f64`); anything that can't round-trip falls back to a /// string so the script always sees a defined value. pub fn json_to_dynamic(value: Json) -> Dynamic { match value { Json::Null => Dynamic::UNIT, Json::Bool(b) => b.into(), Json::Number(n) => { if let Some(i) = n.as_i64() { i.into() } else if let Some(f) = n.as_f64() { f.into() } else { n.to_string().into() } } Json::String(s) => s.into(), Json::Array(arr) => arr .into_iter() .map(json_to_dynamic) .collect::>() .into(), Json::Object(obj) => { let mut m = Map::new(); for (k, v) in obj { m.insert(k.into(), json_to_dynamic(v)); } Dynamic::from(m) } } } /// Convert a Rhai `Dynamic` back to a `serde_json::Value`. Custom Rhai /// types (timestamps, user-registered modules) fall back to their /// `Display` form so they appear as strings in JSON output rather than /// failing the response build. pub fn dynamic_to_json(value: &Dynamic) -> Json { if value.is_unit() { return Json::Null; } if let Ok(b) = value.as_bool() { return Json::Bool(b); } if let Ok(i) = value.as_int() { return Json::Number(i.into()); } if let Ok(f) = value.as_float() { return serde_json::Number::from_f64(f).map_or(Json::Null, Json::Number); } if value.is_string() { return Json::String(value.clone().into_string().unwrap_or_default()); } if let Some(arr) = value.clone().try_cast::() { return Json::Array(arr.iter().map(dynamic_to_json).collect()); } if let Some(map) = value.clone().try_cast::() { let mut out = serde_json::Map::new(); for (k, v) in map { out.insert(k.to_string(), dynamic_to_json(&v)); } return Json::Object(out); } Json::String(value.to_string()) }