use crate::{Dict, Environment, FALSE_SYMBOL, TRUE_SYMBOL, eval::apply::eval_apply, value::Value};
pub static JSON_TYPE_NAME: std::sync::LazyLock<std::sync::Arc<str>> = std::sync::LazyLock::new(|| std::sync::Arc::from("JSON"));
pub(crate) fn from_json(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [json_expr] = args {
let val = eval_apply(json_expr, env)?;
match val {
Value::String(json_text) => {
let values: serde_json::Value = match serde_json::from_str(json_text.as_ref()) {
Ok(v) => v,
Err(e) => {
return Err(std::sync::Arc::from(format!(
concat!("Error[ksl::builtin::FromJSON]: ", "Invalid JSON with `{}`."),
e
)));
}
};
json_value_to_ksl_value(values)
}
e => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::FromJSON]: ",
"Expected a String, but got `{}`."
),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::FromJSON]: ",
"Expected 1 parameter, but {} were passed."
),
args.len()
)))
}
}
fn json_value_to_ksl_value(jsv: serde_json::Value) -> Result<Value, std::sync::Arc<str>> {
match jsv {
serde_json::Value::Null => Ok(Value::Unit),
serde_json::Value::Bool(p) => Ok(if p {
TRUE_SYMBOL.clone()
} else {
FALSE_SYMBOL.clone()
}),
serde_json::Value::Number(number) => match number.as_f64() {
Some(num) => Ok(Value::Number(num)),
None => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::json_value_to_ksl_value]: ",
"Failed to convert `{}` to f64."
),
number
))),
},
serde_json::Value::String(s) => Ok(Value::String(std::sync::Arc::from(s))),
serde_json::Value::Array(values) => values
.into_iter()
.map(json_value_to_ksl_value)
.collect::<Result<std::vec::Vec<Value>, std::sync::Arc<str>>>()
.map(|lst| Value::List(std::sync::Arc::from(lst))),
serde_json::Value::Object(map) => map
.into_iter()
.map(|(k, v)| json_value_to_ksl_value(v).map(|val| (std::sync::Arc::from(k), std::sync::Arc::new(val))))
.collect::<Result<Dict, std::sync::Arc<str>>>()
.map(|dict| Value::Object(JSON_TYPE_NAME.clone(), std::boxed::Box::new(dict))),
}
}
pub(crate) fn to_json(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [ksl_expr] = args {
let val = eval_apply(ksl_expr, env)?;
match val {
ksv @ (Value::Object(_, _) | Value::List(_)) => ksl_value_to_json_value(&ksv)
.and_then(|json| {
serde_json::to_string_pretty(&json).map_err(|e| {
std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ToJSON]: ",
"Failed to stringify with `{}`."
),
e
))
})
})
.map(|s| Value::String(std::sync::Arc::from(s))),
e => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ToJSON]: ",
"Expected an Object or List, but got `{}`."
),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ToJSON]: ",
"Expected 1 parameter, but {} were passed."
),
args.len()
)))
}
}
fn ksl_value_to_json_value(ksv: &Value) -> Result<serde_json::Value, std::sync::Arc<str>> {
match ksv {
Value::Unit => Ok(serde_json::Value::Null),
p @ Value::Atom(_) if p == &*TRUE_SYMBOL => Ok(serde_json::Value::Bool(true)),
p @ Value::Atom(_) if p == &*FALSE_SYMBOL => Ok(serde_json::Value::Bool(false)),
Value::Atom(a) => Ok(serde_json::Value::String(a.to_string())),
Value::String(s) => Ok(serde_json::Value::String(s.to_string())),
Value::Number(num) => match serde_json::Number::from_f64(*num) {
Some(n) => Ok(serde_json::Value::Number(n)),
None => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ksl_value_to_json_value]: ",
"Failed to convert `{}` to JSON number."
),
num
))),
},
Value::List(values) => values
.iter()
.map(ksl_value_to_json_value)
.collect::<Result<std::vec::Vec<serde_json::Value>, std::sync::Arc<str>>>()
.map(serde_json::Value::Array),
Value::Object(_, dict) => dict
.iter()
.map(|(k, v)| ksl_value_to_json_value(v.as_ref()).map(|value| (k.to_string(), value)))
.collect::<Result<serde_json::map::Map<std::string::String, serde_json::Value>, std::sync::Arc<str>>>()
.map(serde_json::Value::Object),
v @ (Value::Module(_, _)
| Value::Symbol(_)
| Value::Lambda(_, _, _)
| Value::Builtin(_)
| Value::Plugin(_, _)
| Value::Thread(_)
| Value::NativeObject(_, _)
| Value::Apply(_, _)) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::ksl_value_to_json_value]: ",
"Invalid KSL value for JSON: `{}`."
),
v
))),
}
}