use crate::{CodecError, LinoValue, decode, encode};
use serde_json::{Map, Value};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct JsonOptions {
pub strip_empty: bool,
}
impl From<Value> for LinoValue {
fn from(value: Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Bool(b) => Self::Bool(b),
Value::Number(n) => Self::JsonNumber(n),
Value::String(s) => Self::String(s),
Value::Array(items) => Self::Array(items.into_iter().map(Self::from).collect()),
Value::Object(pairs) => {
Self::Object(pairs.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
}
}
}
}
impl From<&Value> for LinoValue {
fn from(value: &Value) -> Self {
Self::from(value.clone())
}
}
impl TryFrom<LinoValue> for Value {
type Error = CodecError;
fn try_from(value: LinoValue) -> Result<Self, Self::Error> {
match value {
LinoValue::Null => Ok(Self::Null),
LinoValue::Bool(b) => Ok(Self::Bool(b)),
LinoValue::Int(i) => Ok(Self::Number(i.into())),
LinoValue::Float(f) => serde_json::Number::from_f64(f)
.map(Self::Number)
.ok_or_else(|| {
CodecError::DecodeError("JSON cannot represent a non-finite float".into())
}),
LinoValue::JsonNumber(n) => Ok(Self::Number(n)),
LinoValue::String(s) => Ok(Self::String(s)),
LinoValue::Array(items) => items
.into_iter()
.map(Self::try_from)
.collect::<Result<Vec<_>, _>>()
.map(Self::Array),
LinoValue::Object(pairs) => {
let mut map = Map::new();
for (key, value) in pairs {
if map.contains_key(&key) {
return Err(CodecError::DecodeError(format!(
"duplicate JSON object key: {key:?}"
)));
}
map.insert(key, Self::try_from(value)?);
}
Ok(Self::Object(map))
}
}
}
}
impl TryFrom<&LinoValue> for Value {
type Error = CodecError;
fn try_from(value: &LinoValue) -> Result<Self, Self::Error> {
Self::try_from(value.clone())
}
}
pub fn strip_empty(value: Value) -> Option<Value> {
match value {
Value::Null => None,
Value::Array(items) => {
let items: Vec<_> = items.into_iter().filter_map(strip_empty).collect();
(!items.is_empty()).then_some(Value::Array(items))
}
Value::Object(pairs) => {
let pairs: Map<_, _> = pairs
.into_iter()
.filter_map(|(k, v)| strip_empty(v).map(|v| (k, v)))
.collect();
(!pairs.is_empty()).then_some(Value::Object(pairs))
}
value => Some(value),
}
}
pub fn from_json_with_options(value: Value, options: JsonOptions) -> LinoValue {
LinoValue::from(if options.strip_empty {
strip_empty(value).unwrap_or(Value::Null)
} else {
value
})
}
pub fn json_to_lino(value: &Value) -> String {
encode(&LinoValue::from(value))
}
pub fn json_to_lino_with_options(value: Value, options: JsonOptions) -> String {
encode(&from_json_with_options(value, options))
}
pub fn lino_to_json(text: &str) -> Result<Value, CodecError> {
Value::try_from(decode(text)?)
}
pub(crate) fn decode_compact_number(
values: &[links_notation::LiNo<String>],
) -> Result<LinoValue, CodecError> {
let [_, links_notation::LiNo::Ref(payload)] = values else {
return Err(CodecError::DecodeError(
"json_number expects one numeric payload".into(),
));
};
payload
.parse::<serde_json::Number>()
.map(LinoValue::JsonNumber)
.map_err(|e| CodecError::DecodeError(format!("invalid JSON number: {e}")))
}