use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
UInt(u64),
Int(i64),
Float(f64),
Str(String),
Bytes(Vec<u8>),
Array(Vec<Value>),
Object(Vec<(String, Value)>),
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::UInt(_) => "uint",
Value::Int(_) => "int",
Value::Float(_) => "float",
Value::Str(_) => "str",
Value::Bytes(_) => "bytes",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::Str(s) => Some(s),
_ => None,
}
}
pub fn as_uint(&self) -> Option<u64> {
match self {
Value::UInt(n) => Some(*n),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Value::Int(n) => Some(*n),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Value::Float(n) => Some(*n),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_array(&self) -> Option<&[Value]> {
match self {
Value::Array(a) => Some(a),
_ => None,
}
}
pub fn as_object(&self) -> Option<&[(String, Value)]> {
match self {
Value::Object(o) => Some(o),
_ => None,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "null"),
Value::Bool(b) => write!(f, "{b}"),
Value::UInt(n) => write!(f, "{n}"),
Value::Int(n) => write!(f, "{n}"),
Value::Float(n) => write!(f, "{n}"),
Value::Str(s) => write!(f, "\"{s}\""),
Value::Bytes(b) => write!(
f,
"b64#{}",
base64::engine::general_purpose::STANDARD.encode(b)
),
Value::Array(items) => {
write!(f, "[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
Value::Object(entries) => {
write!(f, "{{ ")?;
for (i, (k, v)) in entries.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{k}: {v};")?;
}
write!(f, " }}")
}
}
}
}
use base64::Engine;
#[derive(Debug, Clone, PartialEq)]
pub enum CrousValue<'a> {
Null,
Bool(bool),
UInt(u64),
Int(i64),
Float(f64),
Str(&'a str),
Bytes(&'a [u8]),
Array(Vec<CrousValue<'a>>),
Object(Vec<(&'a str, CrousValue<'a>)>),
}
impl<'a> CrousValue<'a> {
pub fn to_owned_value(&self) -> Value {
match self {
CrousValue::Null => Value::Null,
CrousValue::Bool(b) => Value::Bool(*b),
CrousValue::UInt(n) => Value::UInt(*n),
CrousValue::Int(n) => Value::Int(*n),
CrousValue::Float(n) => Value::Float(*n),
CrousValue::Str(s) => Value::Str((*s).to_string()),
CrousValue::Bytes(b) => Value::Bytes(b.to_vec()),
CrousValue::Array(items) => {
Value::Array(items.iter().map(|v| v.to_owned_value()).collect())
}
CrousValue::Object(entries) => Value::Object(
entries
.iter()
.map(|(k, v)| ((*k).to_string(), v.to_owned_value()))
.collect(),
),
}
}
}
impl From<&serde_json::Value> for Value {
fn from(jv: &serde_json::Value) -> Self {
match jv {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Bool(*b),
serde_json::Value::Number(n) => {
if let Some(u) = n.as_u64() {
Value::UInt(u)
} else if let Some(i) = n.as_i64() {
Value::Int(i)
} else if let Some(f) = n.as_f64() {
Value::Float(f)
} else {
Value::Null
}
}
serde_json::Value::String(s) => Value::Str(s.clone()),
serde_json::Value::Array(arr) => Value::Array(arr.iter().map(Value::from).collect()),
serde_json::Value::Object(map) => Value::Object(
map.iter()
.map(|(k, v)| (k.clone(), Value::from(v)))
.collect(),
),
}
}
}
impl From<&Value> for serde_json::Value {
fn from(cv: &Value) -> Self {
match cv {
Value::Null => serde_json::Value::Null,
Value::Bool(b) => serde_json::Value::Bool(*b),
Value::UInt(n) => serde_json::json!(*n),
Value::Int(n) => serde_json::json!(*n),
Value::Float(n) => serde_json::json!(*n),
Value::Str(s) => serde_json::Value::String(s.clone()),
Value::Bytes(b) => {
serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
}
Value::Array(items) => {
serde_json::Value::Array(items.iter().map(serde_json::Value::from).collect())
}
Value::Object(entries) => {
let map: serde_json::Map<String, serde_json::Value> = entries
.iter()
.map(|(k, v)| (k.clone(), serde_json::Value::from(v)))
.collect();
serde_json::Value::Object(map)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_type_names() {
assert_eq!(Value::Null.type_name(), "null");
assert_eq!(Value::Bool(true).type_name(), "bool");
assert_eq!(Value::UInt(42).type_name(), "uint");
assert_eq!(Value::Str("hi".into()).type_name(), "str");
}
#[test]
fn crous_value_to_owned() {
let cv = CrousValue::Object(vec![
("name", CrousValue::Str("Alice")),
("age", CrousValue::UInt(30)),
]);
let owned = cv.to_owned_value();
assert_eq!(
owned,
Value::Object(vec![
("name".into(), Value::Str("Alice".into())),
("age".into(), Value::UInt(30)),
])
);
}
#[test]
fn json_roundtrip() {
let cv = Value::Object(vec![
("name".into(), Value::Str("Bob".into())),
("score".into(), Value::Float(99.5)),
]);
let jv = serde_json::Value::from(&cv);
let back = Value::from(&jv);
match &back {
Value::Object(entries) => {
assert_eq!(entries[0].0, "name");
assert_eq!(entries[1].0, "score");
}
_ => panic!("expected object"),
}
}
}