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}