1use sonic_rs::{Array, JsonContainerTrait, JsonType, JsonValueTrait, Object, Value};
10use std::fmt;
11use std::fmt::Write as _;
12
13#[derive(Clone, Copy)]
16pub struct Config {
17 pub delimiter: u8,
19 pub key_folding: bool,
23 pub flatten_depth: Option<usize>,
25 pub empty_array_bare: bool,
29 pub escape_controls: bool,
33 pub max_depth: usize,
40 pub max_input_bytes: usize,
43}
44
45impl Default for Config {
46 fn default() -> Self {
47 Self {
48 delimiter: b',',
49 key_folding: false,
50 flatten_depth: None,
51 empty_array_bare: true,
52 escape_controls: true,
53 max_depth: 1000,
54 max_input_bytes: 0,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum EncodeError {
63 MaxInputBytes { size: usize, limit: usize },
65 MaxDepth { depth: usize, limit: usize },
67 JsonParse(String),
69 Delimiter,
71}
72
73impl fmt::Display for EncodeError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::MaxInputBytes { size, limit } => {
77 write!(f, "input exceeds max_input_bytes ({size} > {limit})")
78 }
79 Self::MaxDepth { depth, limit } => {
80 write!(f, "input exceeds max_depth ({depth} > {limit})")
81 }
82 Self::JsonParse(msg) => write!(f, "JSON parse error: {msg}"),
83 Self::Delimiter => write!(f, "delimiter must be ',', '\\t', or '|'"),
84 }
85 }
86}
87
88impl std::error::Error for EncodeError {}
89
90#[allow(dead_code)]
93fn _assert_encode_error_is_std_error() {
94 fn requires<E: std::error::Error>(_: &E) {}
95 requires(&EncodeError::Delimiter);
96}
97
98pub fn encode(json_bytes: &[u8]) -> Result<String, EncodeError> {
99 encode_with(json_bytes, &Config::default())
100}
101
102pub fn encode_with(json_bytes: &[u8], cfg: &Config) -> Result<String, EncodeError> {
103 if cfg.max_input_bytes != 0 && json_bytes.len() > cfg.max_input_bytes {
104 return Err(EncodeError::MaxInputBytes {
105 size: json_bytes.len(),
106 limit: cfg.max_input_bytes,
107 });
108 }
109 if cfg.max_depth != 0 {
114 if let Some(depth) = scan_exceeds_depth(json_bytes, cfg.max_depth) {
115 return Err(EncodeError::MaxDepth {
116 depth,
117 limit: cfg.max_depth,
118 });
119 }
120 }
121 let value: Value =
122 sonic_rs::from_slice(json_bytes).map_err(|e| EncodeError::JsonParse(e.to_string()))?;
123 let mut out = String::with_capacity(json_bytes.len());
124 match cfg.delimiter {
125 b',' => write_root::<b','>(&value, cfg, &mut out),
126 b'\t' => write_root::<b'\t'>(&value, cfg, &mut out),
127 b'|' => write_root::<b'|'>(&value, cfg, &mut out),
128 _ => return Err(EncodeError::Delimiter),
129 }
130 Ok(out)
131}
132
133fn write_root<const DELIM: u8>(v: &Value, cfg: &Config, out: &mut String) {
134 match v.get_type() {
135 JsonType::Object => {
136 let m = v.as_object().unwrap();
137 if let Some(fields) = keyed_fields(m) {
138 write_keyed_table::<DELIM>(m, &fields, 0, cfg, out);
140 } else if !m.is_empty() {
141 write_object_body::<DELIM>(m, 0, cfg, cfg.key_folding, out);
144 }
145 }
146 JsonType::Array => {
147 let arr = v.as_array().unwrap();
148 if arr.is_empty() && cfg.empty_array_bare {
150 out.push_str("[]");
151 } else {
152 write_array_suffix::<DELIM>(arr, 0, cfg, true, out);
153 }
154 }
155 _ => write_scalar::<DELIM>(v, cfg, out),
156 }
157}
158
159fn write_object_body<const DELIM: u8>(
160 m: &Object,
161 indent: usize,
162 cfg: &Config,
163 allow_fold: bool,
164 out: &mut String,
165) {
166 let mut first = true;
167 for (k, v) in m.iter() {
168 if !first {
169 out.push('\n');
170 }
171 first = false;
172 write_indent(indent, out);
173
174 if allow_fold {
175 if let Some((joined, final_v)) = try_fold(k, v, cfg, m) {
176 write_key(&joined, cfg, out);
177 write_value_after_key::<DELIM>(final_v, indent, cfg, out);
178 continue;
179 }
180 }
181
182 write_key(k, cfg, out);
183 write_value_after_key::<DELIM>(v, indent, cfg, out);
184 }
185}
186
187fn try_fold<'a>(k: &'a str, v: &'a Value, cfg: &Config, m: &Object) -> Option<(String, &'a Value)> {
188 let max_depth = cfg.flatten_depth.unwrap_or(usize::MAX);
189 if max_depth < 2 {
190 return None;
191 }
192
193 if key_needs_quoting(k) {
195 return None;
196 }
197
198 let mut cur_v = v;
199 let mut path: Vec<&'a str> = vec![k];
200
201 loop {
202 if path.len() >= max_depth {
203 break;
204 }
205 let obj = match cur_v.get_type() {
206 JsonType::Object => cur_v.as_object().unwrap(),
207 _ => break,
208 };
209 if obj.len() != 1 {
210 break;
211 }
212 let (nk, nv) = obj.iter().next().unwrap();
213 if key_needs_quoting(nk) {
214 break;
215 }
216 path.push(nk);
217 cur_v = nv;
218 }
219
220 if path.len() < 2 {
221 return None;
222 }
223
224 let joined: String = path.join(".");
225
226 if m.get(&joined).is_some() {
227 return None;
228 }
229
230 Some((joined, cur_v))
231}
232
233fn write_value_after_key<const DELIM: u8>(
234 v: &Value,
235 key_indent: usize,
236 cfg: &Config,
237 out: &mut String,
238) {
239 match v.get_type() {
240 JsonType::Object => {
241 let child = v.as_object().unwrap();
242 if child.is_empty() {
243 out.push(':');
244 } else if let Some(fields) = keyed_fields(child) {
245 write_keyed_table::<DELIM>(child, &fields, key_indent, cfg, out);
248 } else {
249 out.push_str(":\n");
250 let allow = cfg.key_folding && child.len() > 1;
255 write_object_body::<DELIM>(child, key_indent + 1, cfg, allow, out);
256 }
257 }
258 JsonType::Array => {
259 let arr = v.as_array().unwrap();
260 if arr.is_empty() && cfg.empty_array_bare {
262 out.push_str(": []");
263 } else {
264 write_array_suffix::<DELIM>(arr, key_indent, cfg, true, out);
265 }
266 }
267 _ => {
268 out.push_str(": ");
269 write_scalar::<DELIM>(v, cfg, out);
270 }
271 }
272}
273
274fn write_empty_array_legacy<const DELIM: u8>(out: &mut String) {
277 out.push_str("[0");
278 if DELIM != b',' {
279 out.push(DELIM as char);
280 }
281 out.push_str("]:");
282}
283
284fn write_field_list<const DELIM: u8>(fields: &[Field], cfg: &Config, out: &mut String) {
287 out.push('{');
288 for (i, f) in fields.iter().enumerate() {
289 if i > 0 {
290 out.push(DELIM as char);
291 }
292 match f {
293 Field::Leaf(k) => write_key(k, cfg, out),
294 Field::Group(k, sub) => {
295 write_key(k, cfg, out);
296 write_field_list::<DELIM>(sub, cfg, out);
297 }
298 }
299 }
300 out.push('}');
301}
302
303fn write_row_cells<const DELIM: u8>(
306 m: &Object,
307 fields: &[Field],
308 cfg: &Config,
309 first: &mut bool,
310 out: &mut String,
311) {
312 for (idx, f) in fields.iter().enumerate() {
313 match f {
314 Field::Leaf(k) => {
315 if !*first {
316 out.push(DELIM as char);
317 }
318 *first = false;
319 write_scalar::<DELIM>(column_value(m, idx, k).unwrap(), cfg, out);
320 }
321 Field::Group(k, sub) => {
322 let child = column_value(m, idx, k).unwrap().as_object().unwrap();
323 write_row_cells::<DELIM>(child, sub, cfg, first, out);
324 }
325 }
326 }
327}
328
329fn write_keyed_table<const DELIM: u8>(
333 m: &Object,
334 fields: &[Field],
335 indent: usize,
336 cfg: &Config,
337 out: &mut String,
338) {
339 out.push('[');
340 let mut len_buf = itoa::Buffer::new();
341 out.push_str(len_buf.format(m.len()));
342 out.push(':');
343 if DELIM != b',' {
344 out.push(DELIM as char);
345 }
346 out.push(']');
347 write_field_list::<DELIM>(fields, cfg, out);
348 out.push(':');
349
350 for (k, v) in m.iter() {
351 out.push('\n');
352 write_indent(indent + 1, out);
353 write_key(k, cfg, out);
354 out.push_str(": ");
355 let mut first = true;
356 write_row_cells::<DELIM>(v.as_object().unwrap(), fields, cfg, &mut first, out);
357 }
358}
359
360fn write_array_suffix<const DELIM: u8>(
361 arr: &Array,
362 indent: usize,
363 cfg: &Config,
364 allow_tabular: bool,
365 out: &mut String,
366) {
367 if arr.is_empty() {
368 write_empty_array_legacy::<DELIM>(out);
369 return;
370 }
371
372 out.push('[');
373 let mut len_buf = itoa::Buffer::new();
374 out.push_str(len_buf.format(arr.len()));
375 if DELIM != b',' {
376 out.push(DELIM as char);
377 }
378 out.push(']');
379
380 if arr.iter().all(is_scalar) {
381 out.push_str(": ");
382 let mut first = true;
383 for v in arr.iter() {
384 if !first {
385 out.push(DELIM as char);
386 }
387 first = false;
388 write_scalar::<DELIM>(v, cfg, out);
389 }
390 return;
391 }
392
393 let shape = if allow_tabular {
397 table_shape(arr)
398 } else {
399 None
400 };
401
402 if let Some(Table::Nested(fields)) = &shape {
403 write_field_list::<DELIM>(fields, cfg, out);
404 out.push(':');
405 for item in arr.iter() {
406 out.push('\n');
407 write_indent(indent + 1, out);
408 let mut first = true;
409 write_row_cells::<DELIM>(item.as_object().unwrap(), fields, cfg, &mut first, out);
410 }
411 return;
412 }
413
414 if let Some(Table::Flat(keys, uniform_order)) = shape {
415 out.push('{');
419 for (i, k) in keys.iter().enumerate() {
420 if i > 0 {
421 out.push(DELIM as char);
422 }
423 write_key(k, cfg, out);
424 }
425 out.push_str("}:");
426 if uniform_order {
427 for item in arr.iter() {
428 let m = item.as_object().unwrap();
429 out.push('\n');
430 write_indent(indent + 1, out);
431 let mut first = true;
432 for (_, v) in m.iter() {
433 if !first {
434 out.push(DELIM as char);
435 }
436 first = false;
437 write_scalar::<DELIM>(v, cfg, out);
438 }
439 }
440 } else {
441 for item in arr.iter() {
442 let m = item.as_object().unwrap();
443 out.push('\n');
444 write_indent(indent + 1, out);
445 let mut first = true;
446 for k in &keys {
447 if !first {
448 out.push(DELIM as char);
449 }
450 first = false;
451 write_scalar::<DELIM>(m.get(k).unwrap(), cfg, out);
452 }
453 }
454 }
455 return;
456 }
457
458 out.push(':');
459 for item in arr.iter() {
460 out.push('\n');
461 write_indent(indent + 1, out);
462 out.push('-');
463 write_list_item::<DELIM>(item, indent + 1, cfg, out);
464 }
465}
466
467fn write_list_item<const DELIM: u8>(v: &Value, l: usize, cfg: &Config, out: &mut String) {
468 match v.get_type() {
469 JsonType::Object => {
470 let m = v.as_object().unwrap();
471 if !m.is_empty() {
472 out.push(' ');
473 write_list_item_object::<DELIM>(m, l, cfg, out);
474 }
475 }
476 JsonType::Array => {
477 out.push(' ');
478 write_array_suffix::<DELIM>(v.as_array().unwrap(), l, cfg, false, out);
480 }
481 _ => {
482 out.push(' ');
483 write_scalar::<DELIM>(v, cfg, out);
484 }
485 }
486}
487
488fn write_list_item_object<const DELIM: u8>(m: &Object, l: usize, cfg: &Config, out: &mut String) {
489 let mut first = true;
490 for (k, v) in m.iter() {
491 if !first {
492 out.push('\n');
493 write_indent(l + 1, out);
494 }
495 first = false;
496 write_key(k, cfg, out);
497 write_value_after_key::<DELIM>(v, l + 1, cfg, out);
498 }
499}
500
501const OPEN: u8 = 1;
507const CLOSE: u8 = 2;
508const QUOTE: u8 = 3;
509
510const CLASS: [u8; 256] = {
511 let mut t = [0u8; 256];
512 t[b'{' as usize] = OPEN;
513 t[b'[' as usize] = OPEN;
514 t[b'}' as usize] = CLOSE;
515 t[b']' as usize] = CLOSE;
516 t[b'"' as usize] = QUOTE;
517 t
518};
519
520fn scan_exceeds_depth(bytes: &[u8], max_depth: usize) -> Option<usize> {
529 let mut depth: usize = 0;
530 let mut i = 0;
531 let n = bytes.len();
532 while i < n {
533 match CLASS[bytes[i] as usize] {
534 OPEN => {
535 depth += 1;
536 if depth > max_depth {
537 return Some(depth);
538 }
539 i += 1;
540 }
541 CLOSE => {
542 depth = depth.saturating_sub(1);
543 i += 1;
544 }
545 QUOTE => {
546 i += 1;
549 loop {
550 let p = memchr::memchr2(b'"', b'\\', &bytes[i..])?;
553 if bytes[i + p] == b'"' {
554 i += p + 1;
555 break;
556 }
557 i += p + 2;
559 if i >= n {
560 return None;
561 }
562 }
563 }
564 _ => i += 1,
565 }
566 }
567 None
568}
569
570const INDENTS: [&str; 9] = [
573 "",
574 " ",
575 " ",
576 " ",
577 " ",
578 " ",
579 " ",
580 " ",
581 " ",
582];
583
584#[inline]
585fn write_indent(level: usize, out: &mut String) {
586 if level < INDENTS.len() {
587 out.push_str(INDENTS[level]);
588 } else {
589 for _ in 0..(level * 2) {
590 out.push(' ');
591 }
592 }
593}
594
595fn is_scalar(v: &Value) -> bool {
596 !matches!(v.get_type(), JsonType::Object | JsonType::Array)
597}
598
599enum Field<'a> {
603 Leaf(&'a str),
604 Group(&'a str, Vec<Field<'a>>),
605}
606
607enum Table<'a> {
609 Flat(Vec<&'a str>, bool),
613 Nested(Vec<Field<'a>>),
615}
616
617#[inline]
620fn column_value<'a>(m: &'a Object, idx: usize, k: &str) -> Option<&'a Value> {
621 match m.iter().nth(idx) {
622 Some((ik, iv)) if ik == k => Some(iv),
623 _ => m.get(&k),
624 }
625}
626
627#[inline]
632fn columns_could_be_uniform(first: &Object) -> bool {
633 !first.is_empty()
634 && first.iter().all(|(_, v)| match v.get_type() {
635 JsonType::Array => false,
636 JsonType::Object => !v.as_object().unwrap().is_empty(),
637 _ => true,
638 })
639}
640
641fn build_fields<'a>(objs: &[&'a Object]) -> Option<Vec<Field<'a>>> {
645 let first = *objs.first()?;
646 if first.is_empty() {
647 return None;
648 }
649 for m in &objs[1..] {
650 if m.len() != first.len() {
651 return None;
652 }
653 }
654
655 let mut fields = Vec::with_capacity(first.len());
656 for (idx, (k, v0)) in first.iter().enumerate() {
657 match v0.get_type() {
658 JsonType::Object => {
659 let sub0 = v0.as_object().unwrap();
660 if sub0.is_empty() {
661 return None;
662 }
663 let mut subs = Vec::with_capacity(objs.len());
664 subs.push(sub0);
665 for m in &objs[1..] {
666 let sub = column_value(m, idx, k)?.as_object()?;
667 if sub.is_empty() {
668 return None;
669 }
670 subs.push(sub);
671 }
672 fields.push(Field::Group(k, build_fields(&subs)?));
673 }
674 JsonType::Array => return None,
677 _ => {
678 for m in &objs[1..] {
679 if !is_scalar(column_value(m, idx, k)?) {
680 return None;
681 }
682 }
683 fields.push(Field::Leaf(k));
684 }
685 }
686 }
687 Some(fields)
688}
689
690fn table_shape<'a>(arr: &'a Array) -> Option<Table<'a>> {
691 if let Some((keys, uniform_order)) = table_keys(arr) {
692 return Some(Table::Flat(keys, uniform_order));
693 }
694 let probe = arr.iter().next()?.as_object()?;
702 if !columns_could_be_uniform(probe)
703 || !probe
704 .iter()
705 .any(|(_, v)| matches!(v.get_type(), JsonType::Object))
706 {
707 return None;
708 }
709
710 let mut objs = Vec::with_capacity(arr.len());
711 for v in arr.iter() {
712 objs.push(v.as_object()?);
713 }
714 Some(Table::Nested(build_fields(&objs)?))
715}
716
717fn keyed_fields<'a>(m: &'a Object) -> Option<Vec<Field<'a>>> {
721 if m.len() < 2 {
722 return None;
723 }
724 let probe = m.iter().next()?.1.as_object()?;
726 if !columns_could_be_uniform(probe) {
727 return None;
728 }
729 let mut objs = Vec::with_capacity(m.len());
730 for (_, v) in m.iter() {
731 objs.push(v.as_object()?);
732 }
733 build_fields(&objs)
734}
735
736fn table_keys<'a>(arr: &'a Array) -> Option<(Vec<&'a str>, bool)> {
737 let first_v = arr.iter().next()?;
738 let first = first_v.as_object()?;
739 if first.is_empty() {
740 return None;
741 }
742 if !first.iter().all(|(_, v)| is_scalar(v)) {
743 return None;
744 }
745 let keys: Vec<&'a str> = first.iter().map(|(k, _)| k).collect();
746 let mut uniform_order = true;
747
748 for item in arr.iter().skip(1) {
749 let m = item.as_object()?;
750 if m.len() != keys.len() {
751 return None;
752 }
753 let mut row_iter = m.iter();
754 for k in &keys {
755 let (ik, iv) = row_iter.next()?;
756 if !is_scalar(iv) {
757 return None;
758 }
759 if ik != *k {
760 uniform_order = false;
761 }
762 }
763 if !uniform_order {
764 for k in &keys {
765 match m.get(k) {
766 Some(v) if is_scalar(v) => {}
767 _ => return None,
768 }
769 }
770 }
771 }
772 Some((keys, uniform_order))
773}
774
775#[inline]
778fn write_scalar<const DELIM: u8>(v: &Value, cfg: &Config, out: &mut String) {
779 match v.get_type() {
780 JsonType::Null => out.push_str("null"),
781 JsonType::Boolean => out.push_str(if v.as_bool().unwrap() {
782 "true"
783 } else {
784 "false"
785 }),
786 JsonType::Number => write_number(v, out),
787 JsonType::String => write_string_value::<DELIM>(v.as_str().unwrap(), cfg, out),
788 _ => unreachable!("write_scalar on non-scalar"),
789 }
790}
791
792fn write_number(v: &Value, out: &mut String) {
793 if let Some(i) = v.as_i64() {
794 let mut buf = itoa::Buffer::new();
795 out.push_str(buf.format(i));
796 return;
797 }
798 if let Some(u) = v.as_u64() {
799 let mut buf = itoa::Buffer::new();
800 out.push_str(buf.format(u));
801 return;
802 }
803 if let Some(f) = v.as_f64() {
807 write_float(f, out);
808 } else {
809 out.push_str("null");
810 }
811}
812
813fn write_float(f: f64, out: &mut String) {
814 if !f.is_finite() {
815 out.push_str("null");
816 return;
817 }
818 if f == 0.0 {
819 out.push('0');
820 return;
821 }
822 if f.fract() == 0.0 && f.abs() < 1e16 {
824 let mut buf = itoa::Buffer::new();
825 out.push_str(buf.format(f as i64));
826 return;
827 }
828 let mut buf = ryu::Buffer::new();
834 let s = buf.format_finite(f);
835 if s.as_bytes().contains(&b'e') {
836 write!(out, "{}", f).unwrap();
838 } else {
839 out.push_str(s);
840 }
841}
842
843#[inline]
846fn write_string_value<const DELIM: u8>(s: &str, cfg: &Config, out: &mut String) {
847 if value_needs_quoting::<DELIM>(s, cfg.escape_controls) {
848 write_quoted(s, cfg.escape_controls, out);
849 } else {
850 out.push_str(s);
851 }
852}
853
854fn write_key(k: &str, cfg: &Config, out: &mut String) {
855 if key_needs_quoting(k) {
856 write_quoted(k, cfg.escape_controls, out);
857 } else {
858 out.push_str(k);
859 }
860}
861
862#[inline]
868fn key_needs_quoting(s: &str) -> bool {
869 if s.is_empty() {
870 return true;
871 }
872 let bytes = s.as_bytes();
873 let start = match bytes[0] {
874 b'@' | b'$' | b'#' => {
875 if bytes.len() < 2 {
876 return true; }
878 1
879 }
880 _ => 0,
881 };
882 let first = bytes[start];
883 if !(first.is_ascii_alphabetic() || first == b'_') {
884 return true;
885 }
886 for &b in &bytes[start + 1..] {
887 if !(b.is_ascii_alphanumeric() || b == b'_' || b == b'.') {
888 return true;
889 }
890 }
891 false
892}
893
894#[inline]
895fn value_needs_quoting<const DELIM: u8>(s: &str, escape_controls: bool) -> bool {
896 if s.is_empty() {
897 return true;
898 }
899 let bytes = s.as_bytes();
900 match bytes[0] {
901 b'-' | b'#' | b' ' | b'\t' => return true,
902 _ => {}
903 }
904 match bytes[bytes.len() - 1] {
905 b' ' | b'\t' => return true,
906 _ => {}
907 }
908 for &b in bytes {
912 match b {
913 b':' | b'\n' | b'\r' | b'\t' | b'"' | b'\\' | b'[' | b']' | b'{' | b'}' => return true,
917 _ if escape_controls && b < 0x20 => return true,
920 _ if b == DELIM => return true,
921 _ => {}
922 }
923 }
924 if matches!(s, "true" | "false" | "null") {
925 return true;
926 }
927 looks_like_number(bytes)
928}
929
930fn looks_like_number(bytes: &[u8]) -> bool {
933 let mut i = 0;
934 if matches!(bytes[0], b'-' | b'+') {
935 i = 1;
936 if i == bytes.len() {
937 return false;
938 }
939 }
940 let mut has_digit = false;
941 while i < bytes.len() && bytes[i].is_ascii_digit() {
942 has_digit = true;
943 i += 1;
944 }
945 if !has_digit {
946 return false;
947 }
948 if i < bytes.len() && bytes[i] == b'.' {
949 i += 1;
950 let mut has_frac = false;
951 while i < bytes.len() && bytes[i].is_ascii_digit() {
952 has_frac = true;
953 i += 1;
954 }
955 if !has_frac {
956 return false;
957 }
958 }
959 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
960 i += 1;
961 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
962 i += 1;
963 }
964 let mut has_exp_digit = false;
965 while i < bytes.len() && bytes[i].is_ascii_digit() {
966 has_exp_digit = true;
967 i += 1;
968 }
969 if !has_exp_digit {
970 return false;
971 }
972 }
973 i == bytes.len()
974}
975
976#[inline]
978fn hex_lower(n: u8) -> u8 {
979 match n {
980 0..=9 => b'0' + n,
981 _ => b'a' + (n - 10),
982 }
983}
984
985fn write_quoted(s: &str, escape_controls: bool, out: &mut String) {
986 out.push('"');
987 let bytes = s.as_bytes();
988 let mut start = 0;
989 for (i, &b) in bytes.iter().enumerate() {
990 let named = matches!(b, b'\\' | b'"' | b'\n' | b'\r' | b'\t');
993 let other_control = escape_controls && b < 0x20;
994 if named || other_control {
995 if start < i {
996 out.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) });
997 }
998 match b {
999 b'\\' => out.push_str("\\\\"),
1000 b'"' => out.push_str("\\\""),
1001 b'\n' => out.push_str("\\n"),
1002 b'\r' => out.push_str("\\r"),
1003 b'\t' => out.push_str("\\t"),
1004 _ => {
1005 out.push_str("\\u00");
1007 out.push(hex_lower(b >> 4) as char);
1008 out.push(hex_lower(b & 0x0f) as char);
1009 }
1010 }
1011 start = i + 1;
1012 }
1013 }
1014 if start < bytes.len() {
1015 out.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..]) });
1016 }
1017 out.push('"');
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022 use super::{encode, encode_with, Config, EncodeError};
1023
1024 fn enc(json: &str) -> String {
1025 encode(json.as_bytes()).unwrap()
1026 }
1027
1028 fn enc_with(json: &str, cfg: &Config) -> String {
1029 encode_with(json.as_bytes(), cfg).unwrap()
1030 }
1031
1032 #[test]
1037 fn test_encode_error_max_depth_carries_fields_and_exact_message() {
1038 let deep = format!("{}1{}", "{\"a\":".repeat(11), "}".repeat(11));
1039 let cfg = Config {
1040 max_depth: 10,
1041 ..Config::default()
1042 };
1043 let err = encode_with(deep.as_bytes(), &cfg).unwrap_err();
1044 assert_eq!(
1045 err,
1046 EncodeError::MaxDepth {
1047 depth: 11,
1048 limit: 10
1049 }
1050 );
1051 assert_eq!(err.to_string(), "input exceeds max_depth (11 > 10)");
1052 }
1053
1054 #[test]
1055 fn test_encode_error_max_input_bytes_carries_fields_and_exact_message() {
1056 let cfg = Config {
1057 max_input_bytes: 1,
1058 ..Config::default()
1059 };
1060 let err = encode_with(b"{}", &cfg).unwrap_err();
1061 assert_eq!(err, EncodeError::MaxInputBytes { size: 2, limit: 1 });
1062 assert_eq!(err.to_string(), "input exceeds max_input_bytes (2 > 1)");
1063 }
1064
1065 #[test]
1066 fn test_encode_error_json_parse_wraps_parser_message() {
1067 let err = encode(b"{oops").unwrap_err();
1068 assert!(matches!(err, EncodeError::JsonParse(_)));
1069 assert!(
1070 err.to_string().starts_with("JSON parse error: "),
1071 "unexpected: {err}"
1072 );
1073 }
1074
1075 #[test]
1076 fn test_encode_error_delimiter_display_is_stable() {
1077 assert_eq!(
1078 EncodeError::Delimiter.to_string(),
1079 "delimiter must be ',', '\\t', or '|'"
1080 );
1081 }
1082
1083 #[test]
1088 fn test_write_number_small_exponent_expands_to_decimal() {
1089 assert_eq!(enc(r#"{"n":1e-6}"#), "n: 0.000001");
1090 assert_eq!(enc(r#"{"n":1e-7}"#), "n: 0.0000001");
1091 }
1092
1093 #[test]
1094 fn test_write_number_integer_valued_float_drops_fraction() {
1095 assert_eq!(enc(r#"{"n":100.0}"#), "n: 100");
1096 assert_eq!(enc(r#"{"n":-2.0}"#), "n: -2");
1097 }
1098
1099 #[test]
1100 fn test_write_number_decimal_full_precision_preserved() {
1101 assert_eq!(enc(r#"{"n":3.14}"#), "n: 3.14");
1102 assert_eq!(enc(r#"{"n":0.3333333333333333}"#), "n: 0.3333333333333333");
1103 assert_eq!(enc(r#"{"n":1234567.89}"#), "n: 1234567.89");
1104 }
1105
1106 #[test]
1107 fn test_write_number_large_magnitude_float_expands_no_exponent() {
1108 assert_eq!(enc(r#"{"n":1e21}"#), "n: 1000000000000000000000");
1109 }
1110
1111 #[test]
1112 fn test_write_number_i64_and_u64_fast_paths() {
1113 assert_eq!(enc(r#"{"n":42}"#), "n: 42");
1114 assert_eq!(
1115 enc(r#"{"n":-9223372036854775808}"#),
1116 "n: -9223372036854775808"
1117 );
1118 assert_eq!(
1119 enc(r#"{"n":18446744073709551615}"#),
1120 "n: 18446744073709551615"
1121 );
1122 }
1123
1124 #[test]
1125 fn test_write_number_beyond_u64_keeps_expanded_form() {
1126 assert_eq!(enc(r#"{"n":1e30}"#), "n: 1000000000000000000000000000000");
1129 }
1130
1131 #[test]
1134 fn test_empty_array_root_is_bare_brackets() {
1135 assert_eq!(enc("[]"), "[]");
1136 }
1137
1138 #[test]
1139 fn test_empty_array_object_field_is_key_bracket() {
1140 assert_eq!(enc(r#"{"a":[]}"#), "a: []");
1141 assert_eq!(enc(r#"{"x":{"a":[]}}"#), "x:\n a: []");
1142 }
1143
1144 #[test]
1145 fn test_empty_array_as_array_element_keeps_legacy_header() {
1146 assert_eq!(enc(r#"{"pairs":[[],[]]}"#), "pairs[2]:\n - [0]:\n - [0]:");
1148 }
1149
1150 #[test]
1151 fn test_empty_array_legacy_form_when_option_off() {
1152 let cfg = Config {
1153 empty_array_bare: false,
1154 ..Config::default()
1155 };
1156 assert_eq!(enc_with("[]", &cfg), "[0]:");
1157 assert_eq!(enc_with(r#"{"a":[]}"#, &cfg), "a[0]:");
1158 }
1159
1160 #[test]
1166 fn test_escape_controls_emits_lowercase_u_escape() {
1167 assert_eq!(enc("{\"s\":\"a\\u001fb\"}"), "s: \"a\\u001fb\"");
1171 assert_eq!(enc("{\"s\":\"a\\u0000b\"}"), "s: \"a\\u0000b\"");
1172 assert_eq!(enc("{\"s\":\"\\u0004\"}"), "s: \"\\u0004\"");
1173 }
1174
1175 #[test]
1176 fn test_escape_controls_keeps_named_escapes() {
1177 assert_eq!(enc(r#"{"s":"a\nb"}"#), "s: \"a\\nb\"");
1178 assert_eq!(enc(r#"{"s":"a\tb"}"#), "s: \"a\\tb\"");
1179 assert_eq!(enc(r#"{"s":"a\rb"}"#), "s: \"a\\rb\"");
1180 }
1181
1182 #[test]
1183 fn test_escape_controls_off_passes_raw_byte() {
1184 let cfg = Config {
1185 escape_controls: false,
1186 ..Config::default()
1187 };
1188 assert_eq!(enc_with("{\"s\":\"a\\u001fb\"}", &cfg), "s: a\u{1f}b");
1189 }
1190
1191 #[test]
1194 fn test_fold_keys_root_chain() {
1195 let cfg = Config {
1196 key_folding: true,
1197 ..Config::default()
1198 };
1199 assert_eq!(enc_with(r#"{"a":{"b":{"c":1}}}"#, &cfg), "a.b.c: 1");
1200 }
1201
1202 #[test]
1203 fn test_fold_keys_restarts_in_multikey_object_body() {
1204 let cfg = Config {
1207 key_folding: true,
1208 ..Config::default()
1209 };
1210 assert_eq!(
1211 enc_with(r#"{"a":{"x":1,"nested":{"b":{"c":2}}}}"#, &cfg),
1212 "a:\n x: 1\n nested.b.c: 2"
1213 );
1214 }
1215
1216 #[test]
1217 fn test_fold_keys_does_not_refold_past_flatten_depth() {
1218 let cfg = Config {
1219 key_folding: true,
1220 flatten_depth: Some(2),
1221 ..Config::default()
1222 };
1223 assert_eq!(
1224 enc_with(r#"{"a":{"b":{"c":{"d":1}}}}"#, &cfg),
1225 "a.b:\n c:\n d: 1"
1226 );
1227 }
1228
1229 #[test]
1230 fn test_fold_keys_skips_sibling_collision_at_any_depth() {
1231 let cfg = Config {
1233 key_folding: true,
1234 ..Config::default()
1235 };
1236 assert_eq!(
1237 enc_with(
1238 r#"{"data":{"meta":{"items":[1,2]}},"data.meta.items":"literal"}"#,
1239 &cfg
1240 ),
1241 "data:\n meta:\n items[2]: 1,2\ndata.meta.items: literal"
1242 );
1243 }
1244
1245 #[test]
1250 fn test_keyed_table_needs_two_entries() {
1251 assert_eq!(enc(r#"{"m":{"a":{"x":1}}}"#), "m:\n a:\n x: 1");
1253 assert_eq!(
1254 enc(r#"{"m":{"a":{"x":1},"b":{"x":2}}}"#),
1255 "m[2:]{x}:\n a: 1\n b: 2"
1256 );
1257 }
1258
1259 #[test]
1260 fn test_keyed_table_rejects_non_uniform_columns() {
1261 assert_eq!(
1264 enc(r#"{"m":{"a":{"x":1},"b":{"y":2}}}"#),
1265 "m:\n a:\n x: 1\n b:\n y: 2"
1266 );
1267 assert_eq!(
1268 enc(r#"{"m":{"a":{"x":1},"b":7}}"#),
1269 "m:\n a:\n x: 1\n b: 7"
1270 );
1271 assert_eq!(
1272 enc(r#"{"m":{"a":{"x":[1]},"b":{"x":[2]}}}"#),
1273 "m:\n a:\n x[1]: 1\n b:\n x[1]: 2"
1274 );
1275 }
1276
1277 #[test]
1278 fn test_keyed_table_not_used_for_array_elements() {
1279 assert_eq!(
1284 enc(r#"{"a":[{"p":{"x":1},"q":{"x":2}},{"p":{"x":3},"q":[9]}]}"#),
1285 "a[2]:\n - p:\n x: 1\n q:\n x: 2\n - p:\n x: 3\n q[1]: 9"
1286 );
1287 }
1288
1289 #[test]
1290 fn test_keyed_eligible_column_becomes_nested_field_group() {
1291 assert_eq!(
1294 enc(r#"{"a":[{"p":{"x":1},"q":{"x":2}}]}"#),
1295 "a[1]{p{x},q{x}}:\n 1,2"
1296 );
1297 }
1298
1299 #[test]
1302 fn test_nested_field_group_rejects_empty_object_column() {
1303 assert_eq!(enc(r#"{"a":[{"n":{}},{"n":{}}]}"#), "a[2]:\n - n:\n - n:");
1306 }
1307
1308 #[test]
1309 fn test_nested_field_group_rejects_mixed_null_and_object_column() {
1310 assert_eq!(
1313 enc(r#"{"a":[{"n":{"x":1}},{"n":null}]}"#),
1314 "a[2]:\n - n:\n x: 1\n - n: null"
1315 );
1316 }
1317
1318 #[test]
1319 fn test_nested_field_group_tolerates_row_key_reordering() {
1320 assert_eq!(
1322 enc(r#"{"a":[{"id":1,"g":{"x":1,"y":2}},{"g":{"y":4,"x":3},"id":2}]}"#),
1323 "a[2]{id,g{x,y}}:\n 1,1,2\n 2,3,4"
1324 );
1325 }
1326
1327 #[test]
1330 fn test_quotes_leading_plus_numeric_like_string() {
1331 assert_eq!(enc(r#"{"a":"+1"}"#), r#"a: "+1""#);
1332 assert_eq!(enc(r#"{"a":"+1.5e-3"}"#), r#"a: "+1.5e-3""#);
1333 assert_eq!(enc(r#"{"a":"+x"}"#), "a: +x");
1335 }
1336
1337 #[test]
1338 fn test_quotes_brackets_and_braces_anywhere_in_value() {
1339 assert_eq!(enc(r#"{"a":"x[1]"}"#), r#"a: "x[1]""#);
1340 assert_eq!(enc(r#"{"a":"a}b"}"#), r#"a: "a}b""#);
1341 }
1342
1343 #[test]
1346 fn test_max_depth_rejects_overdeep_input_before_parse() {
1347 let deep: Vec<u8> = b"["
1350 .iter()
1351 .cycle()
1352 .take(100)
1353 .chain(b"1".iter())
1354 .chain(b"]".iter().cycle().take(100))
1355 .copied()
1356 .collect();
1357 let cfg = Config {
1358 max_depth: 10,
1359 ..Config::default()
1360 };
1361 let err = encode_with(&deep, &cfg).unwrap_err();
1362 assert!(err.to_string().contains("max_depth"), "got: {err}");
1363 }
1364
1365 #[test]
1366 fn test_max_depth_default_allows_normal_nesting() {
1367 assert_eq!(enc(r#"{"a":{"b":{"c":1}}}"#), "a:\n b:\n c: 1");
1369 }
1370
1371 #[test]
1372 fn test_max_depth_ignores_brackets_inside_strings() {
1373 let cfg = Config {
1375 max_depth: 2,
1376 ..Config::default()
1377 };
1378 assert_eq!(
1379 enc_with(r#"{"s":"[[[[[deep]]]]]"}"#, &cfg),
1380 r#"s: "[[[[[deep]]]]]""#
1381 );
1382 }
1383
1384 #[test]
1387 fn test_max_input_bytes_rejects_oversize_input() {
1388 let cfg = Config {
1389 max_input_bytes: 4,
1390 ..Config::default()
1391 };
1392 let err = encode_with(br#"{"a":1}"#, &cfg).unwrap_err();
1393 assert!(err.to_string().contains("max_input_bytes"), "got: {err}");
1394 }
1395
1396 #[test]
1397 fn test_max_input_bytes_zero_disables_check() {
1398 assert_eq!(enc(r#"{"a":1}"#), "a: 1");
1400 }
1401
1402 #[test]
1405 fn test_ryu_regular_floats_match_spec_form() {
1406 assert_eq!(enc(r#"{"n":2.5}"#), "n: 2.5");
1408 assert_eq!(enc(r#"{"n":99.99}"#), "n: 99.99");
1409 assert_eq!(enc(r#"{"n":0.1}"#), "n: 0.1");
1410 assert_eq!(enc(r#"{"n":-0.0625}"#), "n: -0.0625");
1411 }
1412}