minimo 0.5.42

terminal ui library combining alot of things from here and there and making it slightly easier to play with
Documentation
#[cfg(feature = "value")]
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::ops::{Index, IndexMut};

#[derive(Clone, PartialEq)]
pub enum Val {
    Null,
    Bool(bool),
    Number(f64),
    String(String),
    Array(Vec<Val>),
    Object(BTreeMap<String, Val>),
}

impl Val {
    pub fn is_null(&self) -> bool { matches!(self, Val::Null) }
    pub fn is_bool(&self) -> bool { matches!(self, Val::Bool(_)) }
    pub fn is_number(&self) -> bool { matches!(self, Val::Number(_)) }
    pub fn is_string(&self) -> bool { matches!(self, Val::String(_)) }
    pub fn is_array(&self) -> bool { matches!(self, Val::Array(_)) }
    pub fn is_object(&self) -> bool { matches!(self, Val::Object(_)) }

    pub fn as_bool(&self) -> Option<bool> {
        if let Val::Bool(b) = self { Some(*b) } else { None }
    }

    pub fn as_number(&self) -> Option<f64> {
        if let Val::Number(n) = self { Some(*n) } else { None }
    }

    pub fn as_str(&self) -> Option<&str> {
        if let Val::String(s) = self { Some(s) } else { None }
    }

    pub fn as_array(&self) -> Option<&[Val]> {
        if let Val::Array(a) = self { Some(a) } else { None }
    }

    pub fn as_object(&self) -> Option<&BTreeMap<String, Val>> {
        if let Val::Object(o) = self { Some(o) } else { None }
    }

    pub fn get(&self, key: &str) -> Option<&Val> {
        self.as_object().and_then(|o| o.get(key))
    }

    pub fn get_mut(&mut self, key: &str) -> Option<&mut Val> {
        if let Val::Object(o) = self {
            o.get_mut(key)
        } else {
            None
        }
    }
}

impl fmt::Debug for Val {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Val::Null => write!(f, "null"),
            Val::Bool(b) => write!(f, "{}", b),
            Val::Number(n) => write!(f, "{}", n),
            Val::String(s) => write!(f, "\"{}\"", s),
            Val::Array(a) => {
                write!(f, "[")?;
                for (i, v) in a.iter().enumerate() {
                    if i > 0 { write!(f, ", ")?; }
                    write!(f, "{:?}", v)?;
                }
                write!(f, "]")
            }
            Val::Object(o) => {
                write!(f, "{{")?;
                for (i, (k, v)) in o.iter().enumerate() {
                    if i > 0 { write!(f, ", ")?; }
                    write!(f, "\"{}\": {:?}", k, v)?;
                }
                write!(f, "}}")
            }
        }
    }
}

impl Index<&str> for Val {
    type Output = Val;

    fn index(&self, key: &str) -> &Self::Output {
        self.get(key).unwrap_or(&Val::Null)
    }
}

impl IndexMut<&str> for Val {
    fn index_mut(&mut self, key: &str) -> &mut Self::Output {
        if let Val::Object(o) = self {
            o.entry(key.to_string()).or_insert(Val::Null)
        } else {
            panic!("Attempted to index into a non-object Val")
        }
    }
}

impl Index<usize> for Val {
    type Output = Val;

    fn index(&self, index: usize) -> &Self::Output {
        if let Val::Array(a) = self {
            a.get(index).unwrap_or(&Val::Null)
        } else {
            &Val::Null
        }
    }
}

impl IndexMut<usize> for Val {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        if let Val::Array(a) = self {
            if index >= a.len() {
                a.resize(index + 1, Val::Null);
            }
            &mut a[index]
        } else {
            panic!("Attempted to index into a non-array Val")
        }
    }
}

// macro_rules! val {
//     (null) => { Val::Null };
//     (true) => { Val::Bool(true) };
//     (false) => { Val::Bool(false) };
//     ($x:expr) => { Val::from($x) };
//     // array val!([1, 2, 3])
//     ([ $($value:tt),* $(,)? ]) => {
//         Val::Array(vec![ $(val!($value)),* ])
//     };
//     // object val!({ "key": "value", "key2": "value2" })
//     ({ $($key:expr => $value:tt),* $(,)? }) => {
//         {
//             let mut map = BTreeMap::new();
//             $(
//                 map.insert($key.to_string(), val!($value));
//             )*
//             Val::Object(map)
//         }
//     };
// }


impl From<bool> for Val {
    fn from(b: bool) -> Self { Val::Bool(b) }
}

impl From<f64> for Val {
    fn from(n: f64) -> Self { Val::Number(n) }
}

impl From<i64> for Val {
    fn from(n: i64) -> Self { Val::Number(n as f64) }
}

impl From<i32> for Val {
    fn from(n: i32) -> Self { Val::Number(n as f64) }
}

impl From<u64> for Val {
    fn from(n: u64) -> Self { Val::Number(n as f64) }
}

impl From<u32> for Val {
    fn from(n: u32) -> Self { Val::Number(n as f64) }
}

impl From<String> for Val {
    fn from(s: String) -> Self { Val::String(s) }
}

impl From<&str> for Val {
    fn from(s: &str) -> Self { Val::String(s.to_string()) }
}

impl<T: Into<Val>> From<Vec<T>> for Val {
    fn from(v: Vec<T>) -> Self {
        Val::Array(v.into_iter().map(Into::into).collect())
    }
}

impl<T: Into<Val>, const N: usize> From<[T; N]> for Val {
    fn from(arr: [T; N]) -> Self {
        Val::Array(arr.into_iter().map(Into::into).collect())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_val_macro() {
        
        // let v = val!({
        //     "null": null,
        //     "bool": true,
        //     "number": 42,
        //     "string": "hello",
        //     "array": [1, 2, 3],
        //     "object": {
        //         "nested": "value"
        //     }
        // });

        // assert!(v["null"].is_null());
        // assert_eq!(v["bool"].as_bool(), Some(true));
        // assert_eq!(v["number"].as_number(), Some(42.0));
        // assert_eq!(v["string"].as_str(), Some("hello"));
        // assert_eq!(v["array"].as_array().map(|a| a.len()), Some(3));
        // assert_eq!(v["object"]["nested"].as_str(), Some("value"));

        // // Non-existent keys return Null
        // assert!(v["non_existent"].is_null());
        // assert!(v["array"]["non_existent"].is_null());
    }

    // #[test]
    // fn test_mutability() {
    //     let mut v = val!({ "key": "old_value" });
    //     v["key"] = "new_value".into();
    //     assert_eq!(v["key"].as_str(), Some("new_value"));

    //     v["new_key"] = 42.into();
    //     assert_eq!(v["new_key"].as_number(), Some(42.0));

    //     let mut arr = val!([1, 2, 3]);
    //     arr[1] = 20.into();
    //     assert_eq!(arr[1].as_number(), Some(20.0));

    //     // Extending array
    //     arr[5] = "new".into();
    //     assert_eq!(arr[5].as_str(), Some("new"));
    //     assert!(arr[3].is_null());
    // }
}