ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::json
//!
//! Built-in function `FromJSON` and `ToJSON`.

use crate::{Dict, Environment, FALSE_SYMBOL, TRUE_SYMBOL, eval::apply::eval_apply, value::Value};

/// Type name identifier for JSON Objects.
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()
        )))
    }
}

/// Convert serde_json::Value to ksl::value::Value
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()
        )))
    }
}

/// Convert ksl::value::Value to serde_json::Value
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
        ))),
    }
}