use mlua::{Lua, Result as LuaResult, UserData, Value as LuaValue};
use serde_json::Value as JsonValue;
#[derive(Clone)]
pub struct RawJson(pub String);
impl UserData for RawJson {}
pub fn json_to_lua(lua: &Lua, json: &JsonValue) -> LuaResult<LuaValue> {
match json {
JsonValue::Null => Ok(LuaValue::Nil),
JsonValue::Bool(b) => Ok(LuaValue::Boolean(*b)),
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(LuaValue::Integer(i))
} else if let Some(f) = n.as_f64() {
Ok(LuaValue::Number(f))
} else {
Ok(LuaValue::Nil)
}
}
JsonValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
JsonValue::Array(arr) => {
let table = lua.create_table()?;
for (i, v) in arr.iter().enumerate() {
table.set(i + 1, json_to_lua(lua, v)?)?;
}
Ok(LuaValue::Table(table))
}
JsonValue::Object(obj) => {
let table = lua.create_table()?;
for (k, v) in obj {
table.set(k.clone(), json_to_lua(lua, v)?)?;
}
Ok(LuaValue::Table(table))
}
}
}
pub fn lua_value_to_json(value: &LuaValue) -> LuaResult<JsonValue> {
match value {
LuaValue::Nil => Ok(JsonValue::Null),
LuaValue::Boolean(b) => Ok(JsonValue::Bool(*b)),
LuaValue::Integer(i) => Ok(JsonValue::Number((*i).into())),
LuaValue::Number(n) => Ok(serde_json::Number::from_f64(*n)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null)),
LuaValue::String(s) => Ok(JsonValue::String(s.to_str()?.to_string())),
LuaValue::Table(t) => {
let mut is_array = true;
let mut max_key = 0i64;
for pair in t.clone().pairs::<LuaValue, LuaValue>() {
let (k, _) = pair?;
match k {
LuaValue::Integer(i) if i > 0 => {
max_key = max_key.max(i);
}
_ => {
is_array = false;
break;
}
}
}
if is_array && max_key > 0 {
let mut arr = Vec::new();
for i in 1..=max_key {
let val: LuaValue = t.get(i)?;
arr.push(lua_value_to_json(&val)?);
}
Ok(JsonValue::Array(arr))
} else {
let mut map = serde_json::Map::new();
for pair in t.clone().pairs::<LuaValue, LuaValue>() {
let (k, v) = pair?;
let key_str = match k {
LuaValue::String(s) => s.to_str()?.to_string(),
LuaValue::Integer(i) => i.to_string(),
LuaValue::Number(n) => n.to_string(),
_ => continue,
};
map.insert(key_str, lua_value_to_json(&v)?);
}
Ok(JsonValue::Object(map))
}
}
_ => Ok(JsonValue::Null),
}
}
pub fn matches_filter(doc: &JsonValue, filter: &JsonValue) -> bool {
match filter {
JsonValue::Object(filter_obj) => {
for (key, filter_value) in filter_obj {
match doc.get(key) {
Some(doc_value) => {
if doc_value != filter_value {
return false;
}
}
None => return false,
}
}
true
}
_ => false,
}
}
#[allow(clippy::only_used_in_recursion)]
pub fn lua_to_json_value(lua: &Lua, value: LuaValue) -> LuaResult<JsonValue> {
match value {
LuaValue::Nil => Ok(JsonValue::Null),
LuaValue::Boolean(b) => Ok(JsonValue::Bool(b)),
LuaValue::Integer(i) => Ok(JsonValue::Number(i.into())),
LuaValue::Number(n) => Ok(serde_json::Number::from_f64(n)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null)),
LuaValue::String(s) => Ok(JsonValue::String(s.to_str()?.to_string())),
LuaValue::Table(t) => {
let mut is_array = true;
let mut max_index = 0;
for pair in t.clone().pairs::<LuaValue, LuaValue>() {
let (k, _) = pair?;
if let LuaValue::Integer(i) = k {
if i > 0 {
max_index = max_index.max(i);
} else {
is_array = false;
break;
}
} else {
is_array = false;
break;
}
}
if is_array && max_index > 0 {
let mut arr = Vec::with_capacity(max_index as usize);
for i in 1..=max_index {
let v: LuaValue = t.get(i)?;
arr.push(lua_to_json_value(lua, v)?);
}
Ok(JsonValue::Array(arr))
} else {
let mut obj = serde_json::Map::new();
for pair in t.pairs::<String, LuaValue>() {
let (k, v) = pair?;
obj.insert(k, lua_to_json_value(lua, v)?);
}
Ok(JsonValue::Object(obj))
}
}
LuaValue::UserData(ud) => {
if let Ok(raw) = ud.borrow::<RawJson>() {
return serde_json::from_str(&raw.0)
.map_err(|e| mlua::Error::external(format!("Invalid RawJson: {}", e)));
}
Ok(JsonValue::Null)
}
_ => Ok(JsonValue::Null), }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_json_lua_roundtrip() {
let lua = Lua::new();
let json = serde_json::json!({
"name": "test",
"count": 42,
"active": true,
"tags": ["a", "b", "c"]
});
let lua_val = json_to_lua(&lua, &json).unwrap();
let back = lua_to_json_value(&lua, lua_val).unwrap();
assert_eq!(json, back);
}
}