1use crate::error::Result;
2use crate::value::Value;
3use indexmap::IndexMap;
4use std::io::Write;
5
6#[derive(Debug, Clone)]
8pub struct SerializeOptions {
9 pub indent_size: usize,
11 pub delimiter: Delimiter,
13 pub use_tabular: bool,
15 pub max_inline_items: usize,
17}
18
19impl Default for SerializeOptions {
20 fn default() -> Self {
21 SerializeOptions {
22 indent_size: 2,
23 delimiter: Delimiter::Comma,
24 use_tabular: true,
25 max_inline_items: 10,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq)]
32pub enum Delimiter {
33 Comma,
35 Tab,
37 Pipe,
39}
40
41impl Delimiter {
42 pub fn as_char(&self) -> char {
44 match self {
45 Delimiter::Comma => ',',
46 Delimiter::Tab => '\t',
47 Delimiter::Pipe => '|',
48 }
49 }
50
51 pub fn as_str(&self) -> &'static str {
53 match self {
54 Delimiter::Comma => ",",
55 Delimiter::Tab => "\t",
56 Delimiter::Pipe => "|",
57 }
58 }
59}
60
61pub struct Serializer<'a> {
63 options: &'a SerializeOptions,
64 output: Vec<u8>,
65}
66
67impl<'a> Serializer<'a> {
68 pub fn new(options: &'a SerializeOptions) -> Self {
70 Serializer {
71 options,
72 output: Vec::new(),
73 }
74 }
75
76 pub fn serialize(mut self, value: &Value) -> Result<String> {
78 self.write_value(value, 0)?;
79 String::from_utf8(self.output).map_err(|_| {
80 crate::error::Error::InvalidUtf8
81 })
82 }
83
84 fn write_value(&mut self, value: &Value, depth: usize) -> Result<()> {
86 match value {
87 Value::Null => {
88 write!(self.output, "null")?;
89 }
90 Value::Bool(true) => {
91 write!(self.output, "true")?;
92 }
93 Value::Bool(false) => {
94 write!(self.output, "false")?;
95 }
96 Value::Number(n) => {
97 write!(self.output, "{}", n)?;
98 }
99 Value::String(s) => {
100 self.write_string(s)?;
101 }
102 Value::Array(arr) => {
103 self.write_array(arr, depth)?;
104 }
105 Value::Object(obj) => {
106 self.write_object(obj, depth)?;
107 }
108 }
109 Ok(())
110 }
111
112 fn write_string(&mut self, s: &str) -> Result<()> {
114 if self.needs_quoting(s) {
115 write!(self.output, "\"{}\"", self.escape_string(s))?;
116 } else {
117 write!(self.output, "{}", s)?;
118 }
119 Ok(())
120 }
121
122 fn needs_quoting(&self, s: &str) -> bool {
124 if s.is_empty() {
125 return true;
126 }
127
128 if s == "true" || s == "false" || s == "null" {
130 return true;
131 }
132
133 if self.is_numeric(s) {
134 return true;
135 }
136
137 let delimiter = self.options.delimiter.as_char();
139 let chars_to_quote = [':', ',', '[', ']', '{', '}', '\n', '\r', '\t', '"', '\'', delimiter];
140
141 s.starts_with(' ')
142 || s.ends_with(' ')
143 || s.starts_with('\t')
144 || s.ends_with('\t')
145 || s.chars().any(|c| chars_to_quote.contains(&c))
146 }
147
148 fn is_numeric(&self, s: &str) -> bool {
150 if s.is_empty() {
151 return false;
152 }
153
154 let mut chars = s.chars().peekable();
155
156 if let Some('-') = chars.peek() {
158 chars.next();
159 }
160
161 let mut has_digits = false;
163 while let Some(c) = chars.peek() {
164 if c.is_ascii_digit() {
165 has_digits = true;
166 chars.next();
167 } else {
168 break;
169 }
170 }
171
172 if !has_digits {
173 return false;
174 }
175
176 if let Some('.') = chars.peek() {
178 chars.next();
179 let mut frac_digits = false;
180 while let Some(c) = chars.peek() {
181 if c.is_ascii_digit() {
182 frac_digits = true;
183 chars.next();
184 } else {
185 break;
186 }
187 }
188 if !frac_digits {
189 return false;
190 }
191 }
192
193 if let Some('e') | Some('E') = chars.peek() {
195 chars.next();
196 if let Some('+') | Some('-') = chars.peek() {
197 chars.next();
198 }
199 let mut exp_digits = false;
200 while let Some(c) = chars.peek() {
201 if c.is_ascii_digit() {
202 exp_digits = true;
203 chars.next();
204 } else {
205 break;
206 }
207 }
208 if !exp_digits {
209 return false;
210 }
211 }
212
213 chars.peek().is_none()
214 }
215
216 fn escape_string(&self, s: &str) -> String {
218 s.replace('\\', "\\\\")
219 .replace('"', "\\\"")
220 .replace('\n', "\\n")
221 .replace('\r', "\\r")
222 .replace('\t', "\\t")
223 }
224
225 fn write_array(&mut self, arr: &[Value], depth: usize) -> Result<()> {
227 if arr.is_empty() {
228 write!(self.output, "[]")?;
230 return Ok(());
231 }
232
233 if self.options.use_tabular && self.is_uniform_object_array(arr) {
235 self.write_tabular_array(arr, depth)?;
236 } else if arr.len() <= self.options.max_inline_items
237 && arr.iter().all(|v| self.is_primitive(v))
238 {
239 self.write_inline_primitive_array(arr)?;
241 } else {
242 self.write_expanded_array(arr, depth)?;
244 }
245
246 Ok(())
247 }
248
249 fn is_uniform_object_array(&self, arr: &[Value]) -> bool {
251 if arr.len() < 2 {
252 return false;
253 }
254
255 let first = match &arr[0] {
256 Value::Object(obj) => obj,
257 _ => return false,
258 };
259
260 let keys: Vec<_> = first.keys().collect();
261
262 for item in arr.iter().skip(1) {
263 match item {
264 Value::Object(obj) => {
265 if obj.len() != keys.len() {
266 return false;
267 }
268 for (i, key) in keys.iter().enumerate() {
269 if obj.keys().nth(i) != Some(*key) {
270 return false;
271 }
272 }
273 }
274 _ => return false,
275 }
276 }
277
278 true
279 }
280
281 fn is_primitive(&self, value: &Value) -> bool {
283 !matches!(value, Value::Object(_) | Value::Array(_))
284 }
285
286 fn write_tabular_array(&mut self, arr: &[Value], depth: usize) -> Result<()> {
288 self.write_expanded_array(arr, depth)
289 }
290
291 fn write_expanded_array(&mut self, arr: &[Value], depth: usize) -> Result<()> {
293 let indent = self.make_indent(depth);
294
295 for item in arr {
296 write!(self.output, "\n{}", indent)?;
297 write!(self.output, "- ")?;
298 self.write_value_inline(item)?;
299 }
300 Ok(())
301 }
302
303 fn write_inline_primitive_array(&mut self, arr: &[Value]) -> Result<()> {
305 let delim = self.options.delimiter.as_str();
306
307 for (i, item) in arr.iter().enumerate() {
308 if i > 0 {
309 write!(self.output, "{}", delim)?;
310 }
311 self.write_value_inline(item)?;
312 }
313 Ok(())
314 }
315
316 fn write_value_inline(&mut self, value: &Value) -> Result<()> {
318 match value {
319 Value::Null => {
320 write!(self.output, "null")?;
321 }
322 Value::Bool(true) => {
323 write!(self.output, "true")?;
324 }
325 Value::Bool(false) => {
326 write!(self.output, "false")?;
327 }
328 Value::Number(n) => {
329 write!(self.output, "{}", n)?;
330 }
331 Value::String(s) => {
332 self.write_string(s)?;
333 }
334 Value::Array(arr) => {
335 write!(self.output, "[")?;
336 for (i, item) in arr.iter().enumerate() {
337 if i > 0 {
338 write!(self.output, ", ")?;
339 }
340 self.write_value_inline(item)?;
341 }
342 write!(self.output, "]")?;
343 }
344 Value::Object(obj) => {
345 write!(self.output, "{{")?;
346 for (i, (k, v)) in obj.iter().enumerate() {
347 if i > 0 {
348 write!(self.output, ", ")?;
349 }
350 self.write_string(k)?;
351 write!(self.output, ": ")?;
352 self.write_value_inline(v)?;
353 }
354 write!(self.output, "}}")?;
355 }
356 }
357 Ok(())
358 }
359
360 fn write_object(&mut self, obj: &IndexMap<String, Value>, depth: usize) -> Result<()> {
362 if obj.is_empty() {
363 return Ok(());
364 }
365
366 let indent = self.make_indent(depth);
367
368 for (i, (key, value)) in obj.iter().enumerate() {
369 if i > 0 || depth > 0 {
370 write!(self.output, "\n")?;
371 }
372 write!(self.output, "{}", indent)?;
373
374 let is_tabular = if let Value::Array(arr) = value {
376 self.options.use_tabular && self.is_uniform_object_array(arr)
377 } else {
378 false
379 };
380
381 self.write_string(key)?;
382
383 if is_tabular {
384 if let Value::Array(arr) = value {
385 self.write_tabular_array_header(arr, depth)?;
386 }
387 } else {
388 write!(self.output, ":")?;
389
390 if let Value::Array(arr) = value {
391 if arr.is_empty() {
392 write!(self.output, " []")?;
393 } else if arr.len() <= self.options.max_inline_items
394 && arr.iter().all(|v| self.is_primitive(v))
395 {
396 write!(self.output, " [{}]: ", arr.len())?;
398 self.write_inline_primitive_array(arr)?;
399 } else {
400 self.write_expanded_array_value(arr, depth + 1)?;
401 }
402 } else if value.is_object() {
403 self.write_nested_object(value, depth + 1)?;
404 } else {
405 write!(self.output, " ")?;
406 self.write_value_inline(value)?;
407 }
408 }
409 }
410
411 Ok(())
412 }
413
414 fn write_tabular_array_header(&mut self, arr: &[Value], depth: usize) -> Result<()> {
416 let first = arr[0].as_object().unwrap();
417 let keys: Vec<_> = first.keys().cloned().collect();
418 let length = arr.len();
419
420 write!(self.output, "[{}]{{", length)?;
421 for (i, key) in keys.iter().enumerate() {
422 if i > 0 {
423 write!(self.output, ",")?;
424 }
425 write!(self.output, "{}", key)?;
426 }
427 write!(self.output, "}}:")?;
428
429 let row_indent = self.make_indent(depth + 1);
431 let delim = self.options.delimiter.as_str();
432
433 for row in arr {
434 write!(self.output, "\n{}", row_indent)?;
435 let obj = row.as_object().unwrap();
436 for (i, key) in keys.iter().enumerate() {
437 if i > 0 {
438 write!(self.output, "{}", delim)?;
439 }
440 let val = obj.get(key).unwrap();
441 self.write_value_inline(val)?;
442 }
443 }
444 Ok(())
445 }
446
447 fn write_expanded_array_value(&mut self, arr: &[Value], depth: usize) -> Result<()> {
449 let indent = self.make_indent(depth);
450
451 for item in arr {
452 write!(self.output, "\n{}", indent)?;
453 write!(self.output, "- ")?;
454 self.write_value_inline(item)?;
455 }
456 Ok(())
457 }
458
459 fn write_nested_object(&mut self, value: &Value, depth: usize) -> Result<()> {
461 if let Value::Object(obj) = value {
462 let indent = self.make_indent(depth);
463
464 for (_i, (key, val)) in obj.iter().enumerate() {
465 write!(self.output, "\n{}", indent)?;
466 self.write_string(key)?;
467 write!(self.output, ":")?;
468
469 if val.is_object() {
470 self.write_nested_object(val, depth + 1)?;
471 } else if let Value::Array(arr) = val {
472 if arr.is_empty() {
473 write!(self.output, " []")?;
474 } else if arr.len() <= self.options.max_inline_items
475 && arr.iter().all(|v| self.is_primitive(v))
476 {
477 write!(self.output, " [{}]: ", arr.len())?;
478 self.write_inline_primitive_array(arr)?;
479 } else {
480 self.write_expanded_array_value(arr, depth + 1)?;
481 }
482 } else {
483 write!(self.output, " ")?;
484 self.write_value_inline(val)?;
485 }
486 }
487 }
488 Ok(())
489 }
490
491 fn make_indent(&self, depth: usize) -> String {
493 " ".repeat(depth * self.options.indent_size)
494 }
495}
496
497pub fn to_string(value: &Value) -> Result<String> {
499 let options = SerializeOptions::default();
500 let serializer = Serializer::new(&options);
501 serializer.serialize(value)
502}
503
504pub fn to_string_pretty(value: &Value, options: &SerializeOptions) -> Result<String> {
506 let serializer = Serializer::new(options);
507 serializer.serialize(value)
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513 use crate::value::Value;
514 use indexmap::IndexMap;
515
516 #[test]
517 fn test_serialize_simple_object() {
518 let mut obj = IndexMap::new();
519 obj.insert("id".to_string(), Value::integer(123));
520 obj.insert("name".to_string(), Value::string("Alice"));
521 obj.insert("active".to_string(), Value::Bool(true));
522
523 let value = Value::Object(obj);
524 let result = to_string(&value).unwrap();
525
526 assert!(result.contains("id: 123"));
527 assert!(result.contains("name: Alice"));
528 assert!(result.contains("active: true"));
529 }
530
531 #[test]
532 fn test_serialize_array_inline() {
533 let value = Value::Array(vec![
534 Value::string("foo"),
535 Value::string("bar"),
536 Value::string("baz"),
537 ]);
538
539 let result = to_string(&value).unwrap();
540 assert_eq!(result, "foo,bar,baz");
541 }
542
543 #[test]
544 fn test_serialize_tabular_array() {
545 let mut row1 = IndexMap::new();
546 row1.insert("id".to_string(), Value::integer(1));
547 row1.insert("name".to_string(), Value::string("Alice"));
548 row1.insert("role".to_string(), Value::string("admin"));
549
550 let mut row2 = IndexMap::new();
551 row2.insert("id".to_string(), Value::integer(2));
552 row2.insert("name".to_string(), Value::string("Bob"));
553 row2.insert("role".to_string(), Value::string("user"));
554
555 let mut obj = IndexMap::new();
556 obj.insert(
557 "users".to_string(),
558 Value::Array(vec![Value::Object(row1), Value::Object(row2)]),
559 );
560
561 let value = Value::Object(obj);
562 let result = to_string(&value).unwrap();
563
564 assert!(result.contains("users[2]{id,name,role}:"));
565 assert!(result.contains("1,Alice,admin"));
566 assert!(result.contains("2,Bob,user"));
567 }
568
569 #[test]
570 fn test_string_quoting() {
571 let mut obj = IndexMap::new();
572 obj.insert("msg".to_string(), Value::string("hello, world"));
573 obj.insert("path".to_string(), Value::string("/home/user"));
574
575 let value = Value::Object(obj);
576 let result = to_string(&value).unwrap();
577
578 assert!(result.contains("\"hello, world\""));
579 assert!(result.contains("path: /home/user"));
580 }
581
582 #[test]
583 fn test_serialize_nested_object() {
584 let mut inner = IndexMap::new();
585 inner.insert("id".to_string(), Value::integer(1));
586 inner.insert("name".to_string(), Value::string("Alice"));
587
588 let mut obj = IndexMap::new();
589 obj.insert("user".to_string(), Value::Object(inner));
590
591 let value = Value::Object(obj);
592 let result = to_string(&value).unwrap();
593
594 assert!(result.contains("user:"));
595 assert!(result.contains(" id: 1"));
596 assert!(result.contains(" name: Alice"));
597 }
598}