use chrono::{DateTime, Utc};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Value {
Null,
Bool(bool),
Num(f64),
Str(String),
Array(Vec<Value>),
Object(IndexMap<String, Value>),
Date(DateTime<Utc>),
}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Value::Null => 0.hash(state),
Value::Bool(b) => b.hash(state),
Value::Num(n) => {
if n.is_nan() {
0.hash(state);
} else {
n.to_bits().hash(state);
}
}
Value::Str(s) => s.hash(state),
Value::Array(arr) => arr.hash(state),
Value::Object(obj) => obj
.iter()
.collect::<std::collections::VecDeque<_>>()
.hash(state),
Value::Date(dt) => dt.hash(state),
}
}
}
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::Num(n) => {
if n.fract() == 0.0 && n.is_finite() {
write!(f, "{}", *n as i64)
} else {
write!(f, "{}", n)
}
}
Value::Str(s) => write!(f, "{}", s),
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
write!(f, "[{}]", items.join(", "))
}
Value::Object(obj) => {
let items: Vec<String> = obj.iter().map(|(k, v)| format!("{}: {}", k, v)).collect();
write!(f, "{{{}}}", items.join(", "))
}
Value::Date(d) => write!(f, "{}", d.format("%Y-%m-%d %H:%M:%S")),
}
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Bool(b)
}
}
impl From<f64> for Value {
fn from(n: f64) -> Self {
Value::Num(n)
}
}
impl From<i64> for Value {
fn from(n: i64) -> Self {
Value::Num(n as f64)
}
}
impl From<i32> for Value {
fn from(n: i32) -> Self {
Value::Num(n as f64)
}
}
impl From<usize> for Value {
fn from(n: usize) -> Self {
Value::Num(n as f64)
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::Str(s)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Str(s.to_string())
}
}
impl From<Vec<Value>> for Value {
fn from(arr: Vec<Value>) -> Self {
Value::Array(arr)
}
}
impl From<IndexMap<String, Value>> for Value {
fn from(obj: IndexMap<String, Value>) -> Self {
Value::Object(obj)
}
}
impl Value {
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Bool(b) => *b,
Value::Num(n) => *n != 0.0,
Value::Str(s) => !s.is_empty(),
Value::Array(arr) => !arr.is_empty(),
Value::Object(obj) => !obj.is_empty(),
Value::Date(_) => true,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Num(_) => "number",
Value::Str(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
Value::Date(_) => "date",
}
}
}
impl From<serde_json::Value> for Value {
fn from(json: serde_json::Value) -> Self {
match json {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Num(i as f64)
} else if let Some(f) = n.as_f64() {
Value::Num(f)
} else {
Value::Num(0.0)
}
}
serde_json::Value::String(s) => Value::Str(s),
serde_json::Value::Array(arr) => {
Value::Array(arr.into_iter().map(Value::from).collect())
}
serde_json::Value::Object(obj) => {
let map: IndexMap<String, Value> =
obj.into_iter().map(|(k, v)| (k, Value::from(v))).collect();
Value::Object(map)
}
}
}
}
impl From<Value> for serde_json::Value {
fn from(val: Value) -> Self {
match val {
Value::Null => serde_json::Value::Null,
Value::Bool(b) => serde_json::Value::Bool(b),
Value::Num(n) => serde_json::Number::from_f64(n)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null),
Value::Str(s) => serde_json::Value::String(s),
Value::Array(arr) => {
serde_json::Value::Array(arr.into_iter().map(serde_json::Value::from).collect())
}
Value::Object(obj) => {
let map: serde_json::Map<String, serde_json::Value> = obj
.into_iter()
.map(|(k, v)| (k, serde_json::Value::from(v)))
.collect();
serde_json::Value::Object(map)
}
Value::Date(d) => serde_json::Value::String(d.to_rfc3339()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_null_truthiness() {
assert!(!Value::Null.is_truthy());
}
#[test]
fn test_bool_truthiness() {
assert!(Value::Bool(true).is_truthy());
assert!(!Value::Bool(false).is_truthy());
}
#[test]
fn test_num_truthiness() {
assert!(Value::Num(1.0).is_truthy());
assert!(!Value::Num(0.0).is_truthy());
}
#[test]
fn test_str_truthiness() {
assert!(Value::Str("hello".to_string()).is_truthy());
assert!(!Value::Str("".to_string()).is_truthy());
}
#[test]
fn test_array_truthiness() {
assert!(Value::Array(vec![Value::Num(1.0)]).is_truthy());
assert!(!Value::Array(vec![]).is_truthy());
}
#[test]
fn test_object_truthiness() {
let mut obj = IndexMap::new();
obj.insert("key".to_string(), Value::Num(1.0));
assert!(Value::Object(obj).is_truthy());
assert!(!Value::Object(IndexMap::new()).is_truthy());
}
#[test]
fn test_type_names() {
assert_eq!(Value::Null.type_name(), "null");
assert_eq!(Value::Bool(true).type_name(), "bool");
assert_eq!(Value::Num(1.0).type_name(), "number");
assert_eq!(Value::Str("s".to_string()).type_name(), "string");
assert_eq!(Value::Array(vec![]).type_name(), "array");
assert_eq!(Value::Object(IndexMap::new()).type_name(), "object");
}
#[test]
fn test_display() {
assert_eq!(Value::Null.to_string(), "null");
assert_eq!(Value::Bool(true).to_string(), "true");
assert_eq!(Value::Num(42.0).to_string(), "42");
assert_eq!(Value::Num(42.5).to_string(), "42.5");
assert_eq!(Value::Str("hello".to_string()).to_string(), "hello");
}
#[test]
fn test_json_roundtrip() {
let original = Value::Object({
let mut obj = IndexMap::new();
obj.insert("name".to_string(), Value::Str("test".to_string()));
obj.insert("count".to_string(), Value::Num(42.0));
obj
});
let json = serde_json::Value::from(original.clone());
let back = Value::from(json);
assert_eq!(original, back);
}
}