μson (uson) is a shorthand for JSON
at main 8.2 kB view raw
1use serde::{Deserialize, Serialize}; 2use std::collections::HashMap; 3 4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 5#[serde(untagged)] 6pub enum Value { 7 Null, 8 Bool(bool), 9 Number(Number), 10 String(String), 11 Array(Vec<Value>), 12 Object(HashMap<String, Value>), 13 Typed { 14 type_name: String, 15 value: Box<Value>, 16 }, 17} 18 19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 20#[serde(untagged)] 21pub enum Number { 22 Integer(i64), 23 Float(f64), 24} 25 26impl Value { 27 pub fn as_bool(&self) -> Option<bool> { 28 match self { 29 Value::Bool(b) => Some(*b), 30 _ => None, 31 } 32 } 33 34 pub fn as_number(&self) -> Option<&Number> { 35 match self { 36 Value::Number(n) => Some(n), 37 _ => None, 38 } 39 } 40 41 pub fn as_string(&self) -> Option<&str> { 42 match self { 43 Value::String(s) => Some(s), 44 _ => None, 45 } 46 } 47 48 pub fn as_array(&self) -> Option<&Vec<Value>> { 49 match self { 50 Value::Array(a) => Some(a), 51 _ => None, 52 } 53 } 54 55 pub fn as_object(&self) -> Option<&HashMap<String, Value>> { 56 match self { 57 Value::Object(o) => Some(o), 58 _ => None, 59 } 60 } 61 62 /// Convert to minimal μson string format (no unnecessary quotes, compact) 63 pub fn to_uson_string(&self) -> String { 64 match self { 65 Value::Null => "null".to_string(), 66 Value::Bool(b) => b.to_string(), 67 Value::Number(Number::Integer(i)) => i.to_string(), 68 Value::Number(Number::Float(f)) => { 69 // Format float 70 let s = f.to_string(); 71 // Avoid scientific notation for simple numbers if possible 72 if s.contains('e') && f.abs() < 1e10 && f.fract() == 0.0 { 73 format!("{:.0}", f) 74 } else { 75 s 76 } 77 } 78 Value::String(s) => { 79 // Use unquoted strings when possible 80 if needs_quoting(s) { 81 format!("\"{}\"", escape_string(s)) 82 } else { 83 s.clone() 84 } 85 } 86 Value::Array(arr) => { 87 if arr.is_empty() { 88 "[]".to_string() 89 } else { 90 let items: Vec<String> = arr 91 .iter() 92 .map(|v| v.to_uson_string()) 93 .collect(); 94 format!("[{}]", items.join(" ")) 95 } 96 } 97 Value::Object(obj) => { 98 if obj.is_empty() { 99 "{}".to_string() 100 } else { 101 let mut items: Vec<String> = obj 102 .iter() 103 .map(|(k, v)| { 104 let key = if needs_quoting(k) { 105 format!("\"{}\"", escape_string(k)) 106 } else { 107 k.clone() 108 }; 109 format!("{}:{}", key, v.to_uson_string()) 110 }) 111 .collect(); 112 items.sort(); // Sort for consistent output 113 format!("{{{}}}", items.join(" ")) 114 } 115 } 116 Value::Typed { type_name, value } => { 117 format!("{}!{}", type_name, value.to_uson_string()) 118 } 119 } 120 } 121 122 /// Convert to pretty μson string format with indentation 123 pub fn to_uson_string_pretty(&self) -> String { 124 self.stringify_pretty(0) 125 } 126 127 fn stringify_pretty(&self, indent: usize) -> String { 128 let indent_str = " ".repeat(indent); 129 let next_indent_str = " ".repeat(indent + 1); 130 131 match self { 132 Value::Null => "null".to_string(), 133 Value::Bool(b) => b.to_string(), 134 Value::Number(Number::Integer(i)) => i.to_string(), 135 Value::Number(Number::Float(f)) => f.to_string(), 136 Value::String(s) => { 137 if needs_quoting(s) { 138 format!("\"{}\"", escape_string(s)) 139 } else { 140 s.clone() 141 } 142 } 143 Value::Array(arr) => { 144 if arr.is_empty() { 145 "[]".to_string() 146 } else if arr.len() <= 3 && arr.iter().all(|v| matches!(v, Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::Null)) { 147 // Keep simple arrays on one line 148 let items: Vec<String> = arr.iter().map(|v| v.to_uson_string()).collect(); 149 format!("[{}]", items.join(" ")) 150 } else { 151 // Multi-line for complex arrays 152 let items: Vec<String> = arr 153 .iter() 154 .map(|v| format!("{}{}", next_indent_str, v.stringify_pretty(indent + 1))) 155 .collect(); 156 format!("[\n{}\n{}]", items.join("\n"), indent_str) 157 } 158 } 159 Value::Object(obj) => { 160 if obj.is_empty() { 161 "{}".to_string() 162 } else { 163 let mut items: Vec<(String, String)> = obj 164 .iter() 165 .map(|(k, v)| { 166 let key = if needs_quoting(k) { 167 format!("\"{}\"", escape_string(k)) 168 } else { 169 k.clone() 170 }; 171 (k.clone(), format!("{}{}: {}", next_indent_str, key, v.stringify_pretty(indent + 1))) 172 }) 173 .collect(); 174 items.sort_by(|a, b| a.0.cmp(&b.0)); 175 let formatted: Vec<String> = items.into_iter().map(|(_, s)| s).collect(); 176 format!("{{\n{}\n{}}}", formatted.join("\n"), indent_str) 177 } 178 } 179 Value::Typed { type_name, value } => { 180 format!("{}!{}", type_name, value.stringify_pretty(indent)) 181 } 182 } 183 } 184 185 /// Convert to JSON string 186 pub fn to_json_string(&self) -> Result<String, serde_json::Error> { 187 serde_json::to_string(self) 188 } 189 190 /// Convert to pretty JSON string 191 pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> { 192 serde_json::to_string_pretty(self) 193 } 194} 195 196fn needs_quoting(s: &str) -> bool { 197 if s.is_empty() { 198 return true; 199 } 200 201 // Check if it's a reserved keyword 202 if matches!(s, "true" | "false" | "null") { 203 return true; 204 } 205 206 // Check if it looks like a number 207 if s.parse::<f64>().is_ok() { 208 return true; 209 } 210 211 // Check if it contains special characters or starts with certain chars 212 for ch in s.chars() { 213 if matches!(ch, ' ' | '\t' | '\n' | '\r' | '"' | '\'' | '#' | ',' | ':' | '[' | ']' | '{' | '}' | '\\' | '!' | '/' | '=' | '<' | '>') { 214 return true; 215 } 216 } 217 218 false 219} 220 221fn escape_string(s: &str) -> String { 222 let mut result = String::new(); 223 for ch in s.chars() { 224 match ch { 225 '"' => result.push_str("\\\""), 226 '\\' => result.push_str("\\\\"), 227 '\n' => result.push_str("\\n"), 228 '\r' => result.push_str("\\r"), 229 '\t' => result.push_str("\\t"), 230 '\x08' => result.push_str("\\b"), 231 '\x0C' => result.push_str("\\f"), 232 _ => result.push(ch), 233 } 234 } 235 result 236} 237 238impl Number { 239 pub fn as_i64(&self) -> Option<i64> { 240 match self { 241 Number::Integer(i) => Some(*i), 242 Number::Float(f) => { 243 if f.fract() == 0.0 && *f >= i64::MIN as f64 && *f <= i64::MAX as f64 { 244 Some(*f as i64) 245 } else { 246 None 247 } 248 } 249 } 250 } 251 252 pub fn as_f64(&self) -> f64 { 253 match self { 254 Number::Integer(i) => *i as f64, 255 Number::Float(f) => *f, 256 } 257 } 258}