use crate::{
error::LuaConversionError,
value::{from_utf8_cow, from_utf8_cow_lossy},
JsonConversionError, LuaNumber, LuaTableEntry, LuaValue,
};
use serde_json::{Map as JsonMap, Number as JsonNumber, Value as JsonValue};
use std::borrow::Borrow;
impl<'a> TryFrom<LuaValue<'a>> for JsonValue {
type Error = JsonConversionError;
fn try_from(value: LuaValue<'a>) -> Result<Self, Self::Error> {
to_json_value(value, JsonConversionOptions::default())
}
}
#[derive(Default, Debug, PartialEq)]
pub struct JsonConversionOptions {
pub lossy_string: bool,
}
pub fn to_json_value(
value: LuaValue<'_>,
opts: impl Borrow<JsonConversionOptions>,
) -> Result<JsonValue, JsonConversionError> {
let opts = opts.borrow();
match value {
LuaValue::Nil => Ok(JsonValue::Null),
LuaValue::String(v) => Ok(JsonValue::from(
if opts.lossy_string {
from_utf8_cow_lossy(v)
} else {
from_utf8_cow(v).map_err(|(e, _)| e)?
}
.to_string(),
)),
LuaValue::Boolean(b) => Ok(JsonValue::Bool(b)),
LuaValue::Number(n) => JsonNumber::try_from(n).map(JsonValue::Number),
LuaValue::Table(items) => {
if items.is_empty() {
return Ok(JsonValue::Object(Default::default()));
}
let mut object = JsonMap::new();
let mut array: Vec<JsonValue> = Vec::new();
let mut array_next_idx = 1;
for entry in items {
match entry {
LuaTableEntry::KeyValue(b) => {
move_array_to_object(&mut array, &mut array_next_idx, &mut object);
let k = match b.0 {
LuaValue::String(k) => if opts.lossy_string {
from_utf8_cow_lossy(k)
} else {
from_utf8_cow(k).map_err(|(e, _)| e)?
}
.to_string(),
LuaValue::Nil => "nil".to_string(),
LuaValue::Boolean(k) => k.to_string(),
LuaValue::Number(k) => k.to_string(),
LuaValue::Table(_items) => {
return Err(JsonConversionError::TableKeyedWithTable);
}
};
object.insert(k, to_json_value(b.1, opts)?);
}
LuaTableEntry::NameValue(b) => {
move_array_to_object(&mut array, &mut array_next_idx, &mut object);
object.insert(b.0.to_string(), to_json_value(b.1, opts)?);
}
LuaTableEntry::Value(v) => {
let v = to_json_value(*v, opts)?;
if object.is_empty() {
array.push(v);
} else {
object.insert(array_next_idx.to_string(), v);
array_next_idx += 1;
}
}
LuaTableEntry::NumberValue(n) => {
let v = JsonNumber::try_from(n).map(JsonValue::Number)?;
if object.is_empty() {
array.push(v);
} else {
object.insert(array_next_idx.to_string(), v);
array_next_idx += 1;
}
}
LuaTableEntry::BooleanValue(b) => {
let v = JsonValue::Bool(b);
if object.is_empty() {
array.push(v);
} else {
object.insert(array_next_idx.to_string(), v);
array_next_idx += 1;
}
}
LuaTableEntry::NilValue => {
let v = JsonValue::Null;
if object.is_empty() {
array.push(v);
} else {
object.insert(array_next_idx.to_string(), v);
array_next_idx += 1;
}
}
}
}
match (object.is_empty(), array.is_empty()) {
(true, true) => unreachable!(),
(false, false) => unreachable!(),
(false, true) => Ok(JsonValue::Object(object)),
(true, false) => Ok(JsonValue::Array(array)),
}
}
}
}
pub fn from_json_value(value: JsonValue) -> Result<LuaValue<'static>, LuaConversionError> {
match value {
JsonValue::Null => Ok(LuaValue::Nil),
JsonValue::Bool(b) => Ok(LuaValue::Boolean(b)),
JsonValue::Number(n) => {
if let Some(v) = n.as_i64() {
Ok(LuaValue::integer(v))
} else if let Some(v) = n.as_f64() {
Ok(LuaValue::float(v))
} else {
Err(LuaConversionError::Number)
}
}
JsonValue::String(s) => Ok(LuaValue::String(s.into_bytes().into())),
JsonValue::Array(a) => {
let r: Result<Vec<LuaTableEntry<'static>>, LuaConversionError> = a
.into_iter()
.map(|e| Ok(from_json_value(e)?.into()))
.collect();
Ok(r?.into())
}
JsonValue::Object(o) => {
let r: Result<Vec<LuaTableEntry<'static>>, LuaConversionError> = o
.into_iter()
.map(|(k, v)| Ok(LuaTableEntry::from((k, from_json_value(v)?))))
.collect();
Ok(r?.into())
}
}
}
#[inline]
fn move_array_to_object(
array: &mut Vec<JsonValue>,
array_next_idx: &mut i64,
object: &mut JsonMap<String, JsonValue>,
) {
if !array.is_empty() {
let array = std::mem::take(array);
for v in array {
object.insert(array_next_idx.to_string(), v);
*array_next_idx += 1;
}
}
}
impl TryFrom<LuaNumber> for JsonNumber {
type Error = JsonConversionError;
fn try_from(value: LuaNumber) -> Result<Self, Self::Error> {
match value {
LuaNumber::Integer(i) => Ok(JsonNumber::from(i)),
LuaNumber::Float(f) => match JsonNumber::from_f64(f) {
Some(o) => Ok(o),
None => {
if f.is_infinite() {
if f.is_sign_positive() {
Err(JsonConversionError::PositiveInfinity)
} else {
Err(JsonConversionError::NegativeInfinity)
}
} else if f.is_nan() {
Err(JsonConversionError::NaN)
} else {
Err(JsonConversionError::Float)
}
}
},
}
}
}