toon_rust/
encode.rs

1//! Encoding TOON format from JSON values
2
3use crate::error::Error;
4use crate::options::EncodeOptions;
5use serde_json::Value;
6use std::io::Write;
7
8/// Encode a JSON value to TOON format
9///
10/// # Arguments
11///
12/// * `value` - The JSON value to encode
13/// * `options` - Optional encoding options
14///
15/// # Returns
16///
17/// A `Result` containing the TOON-formatted string or an error
18pub fn encode(value: &Value, options: Option<&EncodeOptions>) -> Result<String, Error> {
19    let default_opts = EncodeOptions::default();
20    let opts = options.unwrap_or(&default_opts);
21    let mut output = String::new();
22    encode_value(value, &mut output, 0, opts)?;
23    Ok(output)
24}
25
26fn encode_value(
27    value: &Value,
28    output: &mut String,
29    indent_level: usize,
30    options: &EncodeOptions,
31) -> Result<(), Error> {
32    match value {
33        Value::Null => {
34            // Null values are typically omitted or represented as empty
35        }
36        Value::Bool(b) => {
37            output.push_str(if *b { "true" } else { "false" });
38        }
39        Value::Number(n) => {
40            if let Some(i) = n.as_i64() {
41                output.push_str(&i.to_string());
42            } else if let Some(f) = n.as_f64() {
43                output.push_str(&f.to_string());
44            } else {
45                return Err(Error::Serialization("Invalid number".to_string()));
46            }
47        }
48        Value::String(s) => {
49            encode_string(s, output, options.get_delimiter());
50        }
51        Value::Array(arr) => {
52            encode_array(arr, output, indent_level, options)?;
53        }
54        Value::Object(obj) => {
55            encode_object(obj, output, indent_level, options)?;
56        }
57    }
58    Ok(())
59}
60
61fn encode_string(s: &str, output: &mut String, delimiter: char) {
62    // Check if we need to quote the string
63    let needs_quoting = s.contains(delimiter)
64        || s.contains(' ')
65        || s.contains('\n')
66        || s.contains('\t')
67        || s == "true"
68        || s == "false"
69        || s == "null"
70        || s.parse::<f64>().is_ok();
71
72    if needs_quoting {
73        output.push('"');
74        for ch in s.chars() {
75            match ch {
76                '"' => output.push_str("\\\""),
77                '\\' => output.push_str("\\\\"),
78                '\n' => output.push_str("\\n"),
79                '\r' => output.push_str("\\r"),
80                '\t' => output.push_str("\\t"),
81                _ => output.push(ch),
82            }
83        }
84        output.push('"');
85    } else {
86        output.push_str(s);
87    }
88}
89
90fn encode_array(
91    arr: &[Value],
92    output: &mut String,
93    indent_level: usize,
94    options: &EncodeOptions,
95) -> Result<(), Error> {
96    if arr.is_empty() {
97        output.push_str("[0]:");
98        return Ok(());
99    }
100
101    // Check if array contains uniform objects (tabular format)
102    if let Some(keys) = check_uniform_objects(arr) {
103        // For root-level arrays, include the header
104        let length_marker = options
105            .length_marker
106            .map(|m| format!("{m}"))
107            .unwrap_or_default();
108        output.push_str(&format!("[{}{}]", length_marker, arr.len()));
109        output.push('{');
110        output.push_str(&keys.join(&options.get_delimiter().to_string()));
111        output.push_str("}:\n");
112        encode_tabular_array_rows(arr, keys, output, indent_level, options)?;
113        return Ok(());
114    }
115
116    // Check if all elements are primitives (inline format)
117    if arr.iter().all(is_primitive) {
118        encode_inline_array(arr, output, options)?;
119        return Ok(());
120    }
121
122    // Otherwise, use list format
123    encode_list_array(arr, output, indent_level, options)?;
124    Ok(())
125}
126
127fn is_primitive(value: &Value) -> bool {
128    matches!(
129        value,
130        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
131    )
132}
133
134fn check_uniform_objects(arr: &[Value]) -> Option<Vec<String>> {
135    if arr.is_empty() {
136        return None;
137    }
138
139    // Get keys from first object (preserve order)
140    let first = arr[0].as_object()?;
141    let keys: Vec<String> = first.keys().cloned().collect();
142    if keys.is_empty() {
143        return None;
144    }
145
146    // Check if all objects have the same keys (order doesn't matter for this check)
147    for item in arr.iter().skip(1) {
148        let obj = item.as_object()?;
149        let item_keys: std::collections::HashSet<String> = obj.keys().cloned().collect();
150        let first_keys: std::collections::HashSet<String> = keys.iter().cloned().collect();
151        if item_keys != first_keys {
152            return None;
153        }
154    }
155
156    Some(keys)
157}
158
159fn encode_tabular_array_rows(
160    arr: &[Value],
161    keys: Vec<String>,
162    output: &mut String,
163    indent_level: usize,
164    options: &EncodeOptions,
165) -> Result<(), Error> {
166    let indent = options.get_indent();
167    let indent_str = " ".repeat(indent_level * indent);
168    let delimiter = options.get_delimiter();
169
170    // Write rows (header already written by caller)
171    for item in arr {
172        output.push_str(&indent_str);
173        output.push_str(&" ".repeat(indent));
174        let obj = item
175            .as_object()
176            .ok_or_else(|| Error::Serialization("Expected object in tabular array".to_string()))?;
177
178        let mut first = true;
179        for key in &keys {
180            if !first {
181                output.push(delimiter);
182            }
183            let value = obj
184                .get(key)
185                .ok_or_else(|| Error::Serialization(format!("Missing key: {key}")))?;
186            encode_primitive_value(value, output, delimiter)?;
187            first = false;
188        }
189        output.push('\n');
190    }
191
192    Ok(())
193}
194
195fn encode_primitive_value(
196    value: &Value,
197    output: &mut String,
198    delimiter: char,
199) -> Result<(), Error> {
200    match value {
201        Value::Null => {}
202        Value::Bool(b) => {
203            output.push_str(if *b { "true" } else { "false" });
204        }
205        Value::Number(n) => {
206            if let Some(i) = n.as_i64() {
207                output.push_str(&i.to_string());
208            } else if let Some(f) = n.as_f64() {
209                output.push_str(&f.to_string());
210            } else {
211                return Err(Error::Serialization("Invalid number".to_string()));
212            }
213        }
214        Value::String(s) => {
215            encode_string(s, output, delimiter);
216        }
217        _ => {
218            return Err(Error::Serialization(
219                "Non-primitive value in tabular array".to_string(),
220            ));
221        }
222    }
223    Ok(())
224}
225
226fn encode_inline_array(
227    arr: &[Value],
228    output: &mut String,
229    options: &EncodeOptions,
230) -> Result<(), Error> {
231    let length_marker = options
232        .length_marker
233        .map(|m| format!("{m}"))
234        .unwrap_or_default();
235    output.push_str(&format!("[{}{}]:", length_marker, arr.len()));
236
237    let delimiter = options.get_delimiter();
238    let mut first = true;
239    for item in arr {
240        if !first {
241            output.push(delimiter);
242        }
243        match item {
244            Value::Null => {}
245            Value::Bool(b) => {
246                output.push_str(if *b { "true" } else { "false" });
247            }
248            Value::Number(n) => {
249                if let Some(i) = n.as_i64() {
250                    output.push_str(&i.to_string());
251                } else if let Some(f) = n.as_f64() {
252                    output.push_str(&f.to_string());
253                }
254            }
255            Value::String(s) => {
256                encode_string(s, output, delimiter);
257            }
258            _ => {
259                return Err(Error::Serialization(
260                    "Non-primitive in inline array".to_string(),
261                ));
262            }
263        }
264        first = false;
265    }
266
267    Ok(())
268}
269
270fn encode_list_array(
271    arr: &[Value],
272    output: &mut String,
273    indent_level: usize,
274    options: &EncodeOptions,
275) -> Result<(), Error> {
276    let indent = options.get_indent();
277    let indent_str = " ".repeat(indent_level * indent);
278
279    for item in arr {
280        output.push_str(&indent_str);
281        output.push_str(&" ".repeat(indent));
282        output.push_str("- ");
283        // For objects in list arrays, encode them inline as key: value
284        match item {
285            Value::Object(obj) => {
286                let mut first = true;
287                for (key, val) in obj {
288                    if !first {
289                        output.push(' ');
290                    }
291                    output.push_str(key);
292                    output.push_str(": ");
293                    encode_primitive_value(val, output, options.get_delimiter())?;
294                    first = false;
295                }
296            }
297            _ => {
298                encode_value(item, output, indent_level + 1, options)?;
299            }
300        }
301        output.push('\n');
302    }
303
304    Ok(())
305}
306
307fn encode_object(
308    obj: &serde_json::Map<String, Value>,
309    output: &mut String,
310    indent_level: usize,
311    options: &EncodeOptions,
312) -> Result<(), Error> {
313    if obj.is_empty() {
314        return Ok(());
315    }
316
317    let indent = options.get_indent();
318    let indent_str = " ".repeat(indent_level * indent);
319
320    let mut first = true;
321    for (key, value) in obj {
322        if !first {
323            output.push('\n');
324        }
325        output.push_str(&indent_str);
326        output.push_str(key);
327
328        match value {
329            Value::Array(arr) => {
330                // For arrays, check the format and encode appropriately
331                if arr.is_empty() {
332                    output.push_str("[0]:");
333                } else if let Some(keys) = check_uniform_objects(arr) {
334                    // Tabular array - output on same line: key[N]{...}:
335                    let length_marker = options
336                        .length_marker
337                        .map(|m| format!("{m}"))
338                        .unwrap_or_default();
339                    output.push_str(&format!("[{}{}]", length_marker, arr.len()));
340                    output.push('{');
341                    output.push_str(&keys.join(&options.get_delimiter().to_string()));
342                    output.push_str("}:\n");
343                    // Now output the rows
344                    encode_tabular_array_rows(arr, keys, output, indent_level, options)?;
345                } else if arr.iter().all(is_primitive) {
346                    // Inline array - output on same line: key[N]: value1,value2
347                    let length_marker = options
348                        .length_marker
349                        .map(|m| format!("{m}"))
350                        .unwrap_or_default();
351                    output.push_str(&format!("[{}{}]:", length_marker, arr.len()));
352                    let delimiter = options.get_delimiter();
353                    let mut first = true;
354                    for item in arr {
355                        if !first {
356                            output.push(delimiter);
357                        }
358                        encode_primitive_value(item, output, delimiter)?;
359                        first = false;
360                    }
361                } else {
362                    // List array - output on same line: key[N]:
363                    let length_marker = options
364                        .length_marker
365                        .map(|m| format!("{m}"))
366                        .unwrap_or_default();
367                    output.push_str(&format!("[{}{}]:", length_marker, arr.len()));
368                    output.push('\n');
369                    encode_list_array(arr, output, indent_level, options)?;
370                }
371            }
372            Value::Object(_) => {
373                output.push_str(": ");
374                output.push('\n');
375                encode_value(value, output, indent_level + 1, options)?;
376            }
377            _ => {
378                output.push_str(": ");
379                encode_value(value, output, indent_level, options)?;
380            }
381        }
382        first = false;
383    }
384
385    Ok(())
386}
387
388/// Encode a JSON value to TOON format and write it to a writer
389///
390/// This function streams the output directly to the writer without building
391/// the entire string in memory, making it suitable for large datasets.
392///
393/// # Arguments
394///
395/// * `value` - The JSON value to encode
396/// * `writer` - The writer to write the TOON-formatted output to
397/// * `options` - Optional encoding options
398///
399/// # Returns
400///
401/// A `Result` indicating success or failure
402///
403/// # Example
404///
405/// ```rust,no_run
406/// use std::fs::File;
407/// use std::io::BufWriter;
408/// use serde_json::json;
409/// use toon_rust::encode_stream;
410///
411/// let data = json!({"name": "Alice", "age": 30});
412/// let file = File::create("output.toon").unwrap();
413/// let mut writer = BufWriter::new(file);
414/// encode_stream(&data, &mut writer, None).unwrap();
415/// ```
416pub fn encode_stream<W: Write>(
417    value: &Value,
418    writer: &mut W,
419    options: Option<&EncodeOptions>,
420) -> Result<(), Error> {
421    let default_opts = EncodeOptions::default();
422    let opts = options.unwrap_or(&default_opts);
423    encode_value_to_writer(value, writer, 0, opts)?;
424    writer.flush().map_err(|e| Error::Io(e.to_string()))?;
425    Ok(())
426}
427
428fn encode_value_to_writer<W: Write>(
429    value: &Value,
430    writer: &mut W,
431    indent_level: usize,
432    options: &EncodeOptions,
433) -> Result<(), Error> {
434    match value {
435        Value::Null => {
436            // Null values are typically omitted or represented as empty
437        }
438        Value::Bool(b) => {
439            let s = if *b { "true" } else { "false" };
440            writer
441                .write_all(s.as_bytes())
442                .map_err(|e| Error::Io(e.to_string()))?;
443        }
444        Value::Number(n) => {
445            if let Some(i) = n.as_i64() {
446                let s = i.to_string();
447                writer
448                    .write_all(s.as_bytes())
449                    .map_err(|e| Error::Io(e.to_string()))?;
450            } else if let Some(f) = n.as_f64() {
451                let s = f.to_string();
452                writer
453                    .write_all(s.as_bytes())
454                    .map_err(|e| Error::Io(e.to_string()))?;
455            } else {
456                return Err(Error::Serialization("Invalid number".to_string()));
457            }
458        }
459        Value::String(s) => {
460            encode_string_to_writer(s, writer, options.get_delimiter())?;
461        }
462        Value::Array(arr) => {
463            encode_array_to_writer(arr, writer, indent_level, options)?;
464        }
465        Value::Object(obj) => {
466            encode_object_to_writer(obj, writer, indent_level, options)?;
467        }
468    }
469    Ok(())
470}
471
472fn encode_string_to_writer<W: Write>(
473    s: &str,
474    writer: &mut W,
475    delimiter: char,
476) -> Result<(), Error> {
477    // Check if we need to quote the string
478    let needs_quoting = s.contains(delimiter)
479        || s.contains(' ')
480        || s.contains('\n')
481        || s.contains('\t')
482        || s == "true"
483        || s == "false"
484        || s == "null"
485        || s.parse::<f64>().is_ok();
486
487    if needs_quoting {
488        writer
489            .write_all(b"\"")
490            .map_err(|e| Error::Io(e.to_string()))?;
491        for ch in s.chars() {
492            match ch {
493                '"' => writer
494                    .write_all(b"\\\"")
495                    .map_err(|e| Error::Io(e.to_string()))?,
496                '\\' => writer
497                    .write_all(b"\\\\")
498                    .map_err(|e| Error::Io(e.to_string()))?,
499                '\n' => writer
500                    .write_all(b"\\n")
501                    .map_err(|e| Error::Io(e.to_string()))?,
502                '\r' => writer
503                    .write_all(b"\\r")
504                    .map_err(|e| Error::Io(e.to_string()))?,
505                '\t' => writer
506                    .write_all(b"\\t")
507                    .map_err(|e| Error::Io(e.to_string()))?,
508                _ => {
509                    let mut buf = [0; 4];
510                    let bytes = ch.encode_utf8(&mut buf).as_bytes();
511                    writer
512                        .write_all(bytes)
513                        .map_err(|e| Error::Io(e.to_string()))?;
514                }
515            }
516        }
517        writer
518            .write_all(b"\"")
519            .map_err(|e| Error::Io(e.to_string()))?;
520    } else {
521        writer
522            .write_all(s.as_bytes())
523            .map_err(|e| Error::Io(e.to_string()))?;
524    }
525    Ok(())
526}
527
528fn encode_array_to_writer<W: Write>(
529    arr: &[Value],
530    writer: &mut W,
531    indent_level: usize,
532    options: &EncodeOptions,
533) -> Result<(), Error> {
534    if arr.is_empty() {
535        writer
536            .write_all(b"[0]:")
537            .map_err(|e| Error::Io(e.to_string()))?;
538        return Ok(());
539    }
540
541    // Check if array contains uniform objects (tabular format)
542    if let Some(keys) = check_uniform_objects(arr) {
543        // For root-level arrays, include the header
544        let length_marker = options
545            .length_marker
546            .map(|m| format!("{m}"))
547            .unwrap_or_default();
548        let header = format!("[{}{}]", length_marker, arr.len());
549        writer
550            .write_all(header.as_bytes())
551            .map_err(|e| Error::Io(e.to_string()))?;
552        writer
553            .write_all(b"{")
554            .map_err(|e| Error::Io(e.to_string()))?;
555        let keys_str = keys.join(&options.get_delimiter().to_string());
556        writer
557            .write_all(keys_str.as_bytes())
558            .map_err(|e| Error::Io(e.to_string()))?;
559        writer
560            .write_all(b"}:\n")
561            .map_err(|e| Error::Io(e.to_string()))?;
562        encode_tabular_array_rows_to_writer(arr, keys, writer, indent_level, options)?;
563        return Ok(());
564    }
565
566    // Check if all elements are primitives (inline format)
567    if arr.iter().all(is_primitive) {
568        encode_inline_array_to_writer(arr, writer, options)?;
569        return Ok(());
570    }
571
572    // Otherwise, use list format
573    encode_list_array_to_writer(arr, writer, indent_level, options)?;
574    Ok(())
575}
576
577fn encode_tabular_array_rows_to_writer<W: Write>(
578    arr: &[Value],
579    keys: Vec<String>,
580    writer: &mut W,
581    indent_level: usize,
582    options: &EncodeOptions,
583) -> Result<(), Error> {
584    let indent = options.get_indent();
585    let indent_str = " ".repeat(indent_level * indent);
586    let delimiter = options.get_delimiter();
587
588    // Write rows (header already written by caller)
589    for item in arr {
590        writer
591            .write_all(indent_str.as_bytes())
592            .map_err(|e| Error::Io(e.to_string()))?;
593        writer
594            .write_all(" ".repeat(indent).as_bytes())
595            .map_err(|e| Error::Io(e.to_string()))?;
596        let obj = item
597            .as_object()
598            .ok_or_else(|| Error::Serialization("Expected object in tabular array".to_string()))?;
599
600        let mut first = true;
601        for key in &keys {
602            if !first {
603                let delim_bytes = [delimiter as u8];
604                writer
605                    .write_all(&delim_bytes)
606                    .map_err(|e| Error::Io(e.to_string()))?;
607            }
608            let value = obj
609                .get(key)
610                .ok_or_else(|| Error::Serialization(format!("Missing key: {key}")))?;
611            encode_primitive_value_to_writer(value, writer, delimiter)?;
612            first = false;
613        }
614        writer
615            .write_all(b"\n")
616            .map_err(|e| Error::Io(e.to_string()))?;
617    }
618
619    Ok(())
620}
621
622fn encode_primitive_value_to_writer<W: Write>(
623    value: &Value,
624    writer: &mut W,
625    delimiter: char,
626) -> Result<(), Error> {
627    match value {
628        Value::Null => {}
629        Value::Bool(b) => {
630            let s = if *b { "true" } else { "false" };
631            writer
632                .write_all(s.as_bytes())
633                .map_err(|e| Error::Io(e.to_string()))?;
634        }
635        Value::Number(n) => {
636            if let Some(i) = n.as_i64() {
637                let s = i.to_string();
638                writer
639                    .write_all(s.as_bytes())
640                    .map_err(|e| Error::Io(e.to_string()))?;
641            } else if let Some(f) = n.as_f64() {
642                let s = f.to_string();
643                writer
644                    .write_all(s.as_bytes())
645                    .map_err(|e| Error::Io(e.to_string()))?;
646            } else {
647                return Err(Error::Serialization("Invalid number".to_string()));
648            }
649        }
650        Value::String(s) => {
651            encode_string_to_writer(s, writer, delimiter)?;
652        }
653        _ => {
654            return Err(Error::Serialization(
655                "Non-primitive value in tabular array".to_string(),
656            ));
657        }
658    }
659    Ok(())
660}
661
662fn encode_inline_array_to_writer<W: Write>(
663    arr: &[Value],
664    writer: &mut W,
665    options: &EncodeOptions,
666) -> Result<(), Error> {
667    let length_marker = options
668        .length_marker
669        .map(|m| format!("{m}"))
670        .unwrap_or_default();
671    let header = format!("[{}{}]:", length_marker, arr.len());
672    writer
673        .write_all(header.as_bytes())
674        .map_err(|e| Error::Io(e.to_string()))?;
675
676    let delimiter = options.get_delimiter();
677    let mut first = true;
678    for item in arr {
679        if !first {
680            let delim_bytes = [delimiter as u8];
681            writer
682                .write_all(&delim_bytes)
683                .map_err(|e| Error::Io(e.to_string()))?;
684        }
685        match item {
686            Value::Null => {}
687            Value::Bool(b) => {
688                let s = if *b { "true" } else { "false" };
689                writer
690                    .write_all(s.as_bytes())
691                    .map_err(|e| Error::Io(e.to_string()))?;
692            }
693            Value::Number(n) => {
694                if let Some(i) = n.as_i64() {
695                    let s = i.to_string();
696                    writer
697                        .write_all(s.as_bytes())
698                        .map_err(|e| Error::Io(e.to_string()))?;
699                } else if let Some(f) = n.as_f64() {
700                    let s = f.to_string();
701                    writer
702                        .write_all(s.as_bytes())
703                        .map_err(|e| Error::Io(e.to_string()))?;
704                }
705            }
706            Value::String(s) => {
707                encode_string_to_writer(s, writer, delimiter)?;
708            }
709            _ => {
710                return Err(Error::Serialization(
711                    "Non-primitive in inline array".to_string(),
712                ));
713            }
714        }
715        first = false;
716    }
717
718    Ok(())
719}
720
721fn encode_list_array_to_writer<W: Write>(
722    arr: &[Value],
723    writer: &mut W,
724    indent_level: usize,
725    options: &EncodeOptions,
726) -> Result<(), Error> {
727    let indent = options.get_indent();
728    let indent_str = " ".repeat(indent_level * indent);
729
730    for item in arr {
731        writer
732            .write_all(indent_str.as_bytes())
733            .map_err(|e| Error::Io(e.to_string()))?;
734        writer
735            .write_all(" ".repeat(indent).as_bytes())
736            .map_err(|e| Error::Io(e.to_string()))?;
737        writer
738            .write_all(b"- ")
739            .map_err(|e| Error::Io(e.to_string()))?;
740        // For objects in list arrays, encode them inline as key: value
741        match item {
742            Value::Object(obj) => {
743                let mut first = true;
744                for (key, val) in obj {
745                    if !first {
746                        writer
747                            .write_all(b" ")
748                            .map_err(|e| Error::Io(e.to_string()))?;
749                    }
750                    writer
751                        .write_all(key.as_bytes())
752                        .map_err(|e| Error::Io(e.to_string()))?;
753                    writer
754                        .write_all(b": ")
755                        .map_err(|e| Error::Io(e.to_string()))?;
756                    encode_primitive_value_to_writer(val, writer, options.get_delimiter())?;
757                    first = false;
758                }
759            }
760            _ => {
761                encode_value_to_writer(item, writer, indent_level + 1, options)?;
762            }
763        }
764        writer
765            .write_all(b"\n")
766            .map_err(|e| Error::Io(e.to_string()))?;
767    }
768
769    Ok(())
770}
771
772fn encode_object_to_writer<W: Write>(
773    obj: &serde_json::Map<String, Value>,
774    writer: &mut W,
775    indent_level: usize,
776    options: &EncodeOptions,
777) -> Result<(), Error> {
778    if obj.is_empty() {
779        return Ok(());
780    }
781
782    let indent = options.get_indent();
783    let indent_str = " ".repeat(indent_level * indent);
784
785    let mut first = true;
786    for (key, value) in obj {
787        if !first {
788            writer
789                .write_all(b"\n")
790                .map_err(|e| Error::Io(e.to_string()))?;
791        }
792        writer
793            .write_all(indent_str.as_bytes())
794            .map_err(|e| Error::Io(e.to_string()))?;
795        writer
796            .write_all(key.as_bytes())
797            .map_err(|e| Error::Io(e.to_string()))?;
798
799        match value {
800            Value::Array(arr) => {
801                // For arrays, check the format and encode appropriately
802                if arr.is_empty() {
803                    writer
804                        .write_all(b"[0]:")
805                        .map_err(|e| Error::Io(e.to_string()))?;
806                } else if let Some(keys) = check_uniform_objects(arr) {
807                    // Tabular array - output on same line: key[N]{...}:
808                    let length_marker = options
809                        .length_marker
810                        .map(|m| format!("{m}"))
811                        .unwrap_or_default();
812                    let header = format!("[{}{}]", length_marker, arr.len());
813                    writer
814                        .write_all(header.as_bytes())
815                        .map_err(|e| Error::Io(e.to_string()))?;
816                    writer
817                        .write_all(b"{")
818                        .map_err(|e| Error::Io(e.to_string()))?;
819                    let keys_str = keys.join(&options.get_delimiter().to_string());
820                    writer
821                        .write_all(keys_str.as_bytes())
822                        .map_err(|e| Error::Io(e.to_string()))?;
823                    writer
824                        .write_all(b"}:\n")
825                        .map_err(|e| Error::Io(e.to_string()))?;
826                    // Now output the rows
827                    encode_tabular_array_rows_to_writer(arr, keys, writer, indent_level, options)?;
828                } else if arr.iter().all(is_primitive) {
829                    // Inline array - output on same line: key[N]: value1,value2
830                    let length_marker = options
831                        .length_marker
832                        .map(|m| format!("{m}"))
833                        .unwrap_or_default();
834                    let header = format!("[{}{}]:", length_marker, arr.len());
835                    writer
836                        .write_all(header.as_bytes())
837                        .map_err(|e| Error::Io(e.to_string()))?;
838                    let delimiter = options.get_delimiter();
839                    let mut first = true;
840                    for item in arr {
841                        if !first {
842                            let delim_bytes = [delimiter as u8];
843                            writer
844                                .write_all(&delim_bytes)
845                                .map_err(|e| Error::Io(e.to_string()))?;
846                        }
847                        encode_primitive_value_to_writer(item, writer, delimiter)?;
848                        first = false;
849                    }
850                } else {
851                    // List array - output on same line: key[N]:
852                    let length_marker = options
853                        .length_marker
854                        .map(|m| format!("{m}"))
855                        .unwrap_or_default();
856                    let header = format!("[{}{}]:", length_marker, arr.len());
857                    writer
858                        .write_all(header.as_bytes())
859                        .map_err(|e| Error::Io(e.to_string()))?;
860                    writer
861                        .write_all(b"\n")
862                        .map_err(|e| Error::Io(e.to_string()))?;
863                    encode_list_array_to_writer(arr, writer, indent_level, options)?;
864                }
865            }
866            Value::Object(_) => {
867                writer
868                    .write_all(b": ")
869                    .map_err(|e| Error::Io(e.to_string()))?;
870                writer
871                    .write_all(b"\n")
872                    .map_err(|e| Error::Io(e.to_string()))?;
873                encode_value_to_writer(value, writer, indent_level + 1, options)?;
874            }
875            _ => {
876                writer
877                    .write_all(b": ")
878                    .map_err(|e| Error::Io(e.to_string()))?;
879                encode_value_to_writer(value, writer, indent_level, options)?;
880            }
881        }
882        first = false;
883    }
884
885    Ok(())
886}