1use sonic_rs::{Array, JsonContainerTrait, JsonType, JsonValueTrait, Object, Value};
10use std::fmt::Write as _;
11
12#[derive(Clone, Copy)]
14pub struct Config {
15 pub delimiter: u8,
17 pub key_folding: bool,
19 pub flatten_depth: Option<usize>,
21 pub empty_array_bare: bool,
24 pub escape_controls: bool,
27 pub max_depth: usize,
34 pub max_input_bytes: usize,
37}
38
39impl Default for Config {
40 fn default() -> Self {
41 Self {
42 delimiter: b',',
43 key_folding: false,
44 flatten_depth: None,
45 empty_array_bare: true,
46 escape_controls: true,
47 max_depth: 1000,
48 max_input_bytes: 0,
49 }
50 }
51}
52
53pub fn encode(json_bytes: &[u8]) -> Result<String, String> {
54 encode_with(json_bytes, &Config::default())
55}
56
57pub fn encode_with(json_bytes: &[u8], cfg: &Config) -> Result<String, String> {
58 if cfg.max_input_bytes != 0 && json_bytes.len() > cfg.max_input_bytes {
59 return Err(format!(
60 "input exceeds max_input_bytes ({} > {})",
61 json_bytes.len(),
62 cfg.max_input_bytes
63 ));
64 }
65 if cfg.max_depth != 0 {
70 if let Some(depth) = scan_exceeds_depth(json_bytes, cfg.max_depth) {
71 return Err(format!(
72 "input exceeds max_depth ({} > {})",
73 depth, cfg.max_depth
74 ));
75 }
76 }
77 let value: Value =
78 sonic_rs::from_slice(json_bytes).map_err(|e| format!("JSON parse error: {}", e))?;
79 let mut out = String::with_capacity(json_bytes.len());
80 match cfg.delimiter {
81 b',' => write_root::<b','>(&value, cfg, &mut out),
82 b'\t' => write_root::<b'\t'>(&value, cfg, &mut out),
83 b'|' => write_root::<b'|'>(&value, cfg, &mut out),
84 _ => return Err("delimiter must be ',', '\\t', or '|'".to_string()),
85 }
86 Ok(out)
87}
88
89fn write_root<const DELIM: u8>(v: &Value, cfg: &Config, out: &mut String) {
90 match v.get_type() {
91 JsonType::Object => {
92 let m = v.as_object().unwrap();
93 if !m.is_empty() {
94 write_object_body::<DELIM>(m, 0, cfg, cfg.key_folding, out);
97 }
98 }
99 JsonType::Array => {
100 let arr = v.as_array().unwrap();
101 if arr.is_empty() && cfg.empty_array_bare {
103 out.push_str("[]");
104 } else {
105 write_array_suffix::<DELIM>(arr, 0, cfg, out);
106 }
107 }
108 _ => write_scalar::<DELIM>(v, cfg, out),
109 }
110}
111
112fn write_object_body<const DELIM: u8>(
113 m: &Object,
114 indent: usize,
115 cfg: &Config,
116 allow_fold: bool,
117 out: &mut String,
118) {
119 let mut first = true;
120 for (k, v) in m.iter() {
121 if !first {
122 out.push('\n');
123 }
124 first = false;
125 write_indent(indent, out);
126
127 if allow_fold {
128 if let Some((joined, final_v)) = try_fold(k, v, cfg, m) {
129 write_key(&joined, cfg, out);
130 write_value_after_key::<DELIM>(final_v, indent, cfg, out);
131 continue;
132 }
133 }
134
135 write_key(k, cfg, out);
136 write_value_after_key::<DELIM>(v, indent, cfg, out);
137 }
138}
139
140fn try_fold<'a>(k: &'a str, v: &'a Value, cfg: &Config, m: &Object) -> Option<(String, &'a Value)> {
141 let max_depth = cfg.flatten_depth.unwrap_or(usize::MAX);
142 if max_depth < 2 {
143 return None;
144 }
145
146 if key_needs_quoting(k) {
148 return None;
149 }
150
151 let mut cur_v = v;
152 let mut path: Vec<&'a str> = vec![k];
153
154 loop {
155 if path.len() >= max_depth {
156 break;
157 }
158 let obj = match cur_v.get_type() {
159 JsonType::Object => cur_v.as_object().unwrap(),
160 _ => break,
161 };
162 if obj.len() != 1 {
163 break;
164 }
165 let (nk, nv) = obj.iter().next().unwrap();
166 if key_needs_quoting(nk) {
167 break;
168 }
169 path.push(nk);
170 cur_v = nv;
171 }
172
173 if path.len() < 2 {
174 return None;
175 }
176
177 let joined: String = path.join(".");
178
179 if m.get(&joined).is_some() {
180 return None;
181 }
182
183 Some((joined, cur_v))
184}
185
186fn write_value_after_key<const DELIM: u8>(
187 v: &Value,
188 key_indent: usize,
189 cfg: &Config,
190 out: &mut String,
191) {
192 match v.get_type() {
193 JsonType::Object => {
194 let child = v.as_object().unwrap();
195 if child.is_empty() {
196 out.push(':');
197 } else {
198 out.push_str(":\n");
199 let allow = cfg.key_folding && child.len() > 1;
204 write_object_body::<DELIM>(child, key_indent + 1, cfg, allow, out);
205 }
206 }
207 JsonType::Array => {
208 let arr = v.as_array().unwrap();
209 if arr.is_empty() && cfg.empty_array_bare {
211 out.push_str(": []");
212 } else {
213 write_array_suffix::<DELIM>(arr, key_indent, cfg, out);
214 }
215 }
216 _ => {
217 out.push_str(": ");
218 write_scalar::<DELIM>(v, cfg, out);
219 }
220 }
221}
222
223fn write_empty_array_legacy<const DELIM: u8>(out: &mut String) {
226 out.push_str("[0");
227 if DELIM != b',' {
228 out.push(DELIM as char);
229 }
230 out.push_str("]:");
231}
232
233fn write_array_suffix<const DELIM: u8>(arr: &Array, indent: usize, cfg: &Config, out: &mut String) {
234 let _ = cfg;
235 if arr.is_empty() {
236 write_empty_array_legacy::<DELIM>(out);
237 return;
238 }
239
240 out.push('[');
241 let mut len_buf = itoa::Buffer::new();
242 out.push_str(len_buf.format(arr.len()));
243 if DELIM != b',' {
244 out.push(DELIM as char);
245 }
246 out.push(']');
247
248 if arr.iter().all(is_scalar) {
249 out.push_str(": ");
250 let mut first = true;
251 for v in arr.iter() {
252 if !first {
253 out.push(DELIM as char);
254 }
255 first = false;
256 write_scalar::<DELIM>(v, cfg, out);
257 }
258 return;
259 }
260
261 if let Some((keys, uniform_order)) = table_keys(arr) {
262 out.push('{');
263 for (i, k) in keys.iter().enumerate() {
264 if i > 0 {
265 out.push(DELIM as char);
266 }
267 write_key(k, cfg, out);
268 }
269 out.push_str("}:");
270 if uniform_order {
271 for item in arr.iter() {
272 let m = item.as_object().unwrap();
273 out.push('\n');
274 write_indent(indent + 1, out);
275 let mut first = true;
276 for (_, v) in m.iter() {
277 if !first {
278 out.push(DELIM as char);
279 }
280 first = false;
281 write_scalar::<DELIM>(v, cfg, out);
282 }
283 }
284 } else {
285 for item in arr.iter() {
286 let m = item.as_object().unwrap();
287 out.push('\n');
288 write_indent(indent + 1, out);
289 let mut first = true;
290 for k in &keys {
291 if !first {
292 out.push(DELIM as char);
293 }
294 first = false;
295 write_scalar::<DELIM>(m.get(k).unwrap(), cfg, out);
296 }
297 }
298 }
299 return;
300 }
301
302 out.push(':');
303 for item in arr.iter() {
304 out.push('\n');
305 write_indent(indent + 1, out);
306 out.push('-');
307 write_list_item::<DELIM>(item, indent + 1, cfg, out);
308 }
309}
310
311fn write_list_item<const DELIM: u8>(v: &Value, l: usize, cfg: &Config, out: &mut String) {
312 match v.get_type() {
313 JsonType::Object => {
314 let m = v.as_object().unwrap();
315 if !m.is_empty() {
316 out.push(' ');
317 write_list_item_object::<DELIM>(m, l, cfg, out);
318 }
319 }
320 JsonType::Array => {
321 out.push(' ');
322 write_array_suffix::<DELIM>(v.as_array().unwrap(), l, cfg, out);
323 }
324 _ => {
325 out.push(' ');
326 write_scalar::<DELIM>(v, cfg, out);
327 }
328 }
329}
330
331fn write_list_item_object<const DELIM: u8>(m: &Object, l: usize, cfg: &Config, out: &mut String) {
332 let mut first = true;
333 for (k, v) in m.iter() {
334 if !first {
335 out.push('\n');
336 write_indent(l + 1, out);
337 }
338 first = false;
339 write_key(k, cfg, out);
340 write_value_after_key::<DELIM>(v, l + 1, cfg, out);
341 }
342}
343
344const OPEN: u8 = 1;
350const CLOSE: u8 = 2;
351const QUOTE: u8 = 3;
352
353const CLASS: [u8; 256] = {
354 let mut t = [0u8; 256];
355 t[b'{' as usize] = OPEN;
356 t[b'[' as usize] = OPEN;
357 t[b'}' as usize] = CLOSE;
358 t[b']' as usize] = CLOSE;
359 t[b'"' as usize] = QUOTE;
360 t
361};
362
363fn scan_exceeds_depth(bytes: &[u8], max_depth: usize) -> Option<usize> {
372 let mut depth: usize = 0;
373 let mut i = 0;
374 let n = bytes.len();
375 while i < n {
376 match CLASS[bytes[i] as usize] {
377 OPEN => {
378 depth += 1;
379 if depth > max_depth {
380 return Some(depth);
381 }
382 i += 1;
383 }
384 CLOSE => {
385 depth = depth.saturating_sub(1);
386 i += 1;
387 }
388 QUOTE => {
389 i += 1;
392 loop {
393 match memchr::memchr2(b'"', b'\\', &bytes[i..]) {
394 Some(p) => {
395 if bytes[i + p] == b'"' {
396 i += p + 1;
397 break;
398 }
399 i += p + 2;
401 if i >= n {
402 return None;
403 }
404 }
405 None => return None, }
407 }
408 }
409 _ => i += 1,
410 }
411 }
412 None
413}
414
415const INDENTS: [&str; 9] = [
418 "",
419 " ",
420 " ",
421 " ",
422 " ",
423 " ",
424 " ",
425 " ",
426 " ",
427];
428
429#[inline]
430fn write_indent(level: usize, out: &mut String) {
431 if level < INDENTS.len() {
432 out.push_str(INDENTS[level]);
433 } else {
434 for _ in 0..(level * 2) {
435 out.push(' ');
436 }
437 }
438}
439
440fn is_scalar(v: &Value) -> bool {
441 !matches!(v.get_type(), JsonType::Object | JsonType::Array)
442}
443
444fn table_keys<'a>(arr: &'a Array) -> Option<(Vec<&'a str>, bool)> {
445 let first_v = arr.iter().next()?;
446 let first = first_v.as_object()?;
447 if first.is_empty() {
448 return None;
449 }
450 if !first.iter().all(|(_, v)| is_scalar(v)) {
451 return None;
452 }
453 let keys: Vec<&'a str> = first.iter().map(|(k, _)| k).collect();
454 let mut uniform_order = true;
455
456 for item in arr.iter().skip(1) {
457 let m = item.as_object()?;
458 if m.len() != keys.len() {
459 return None;
460 }
461 let mut row_iter = m.iter();
462 for k in &keys {
463 let (ik, iv) = row_iter.next()?;
464 if !is_scalar(iv) {
465 return None;
466 }
467 if ik != *k {
468 uniform_order = false;
469 }
470 }
471 if !uniform_order {
472 for k in &keys {
473 match m.get(k) {
474 Some(v) if is_scalar(v) => {}
475 _ => return None,
476 }
477 }
478 }
479 }
480 Some((keys, uniform_order))
481}
482
483#[inline]
486fn write_scalar<const DELIM: u8>(v: &Value, cfg: &Config, out: &mut String) {
487 match v.get_type() {
488 JsonType::Null => out.push_str("null"),
489 JsonType::Boolean => out.push_str(if v.as_bool().unwrap() {
490 "true"
491 } else {
492 "false"
493 }),
494 JsonType::Number => write_number(v, out),
495 JsonType::String => write_string_value::<DELIM>(v.as_str().unwrap(), cfg, out),
496 _ => unreachable!("write_scalar on non-scalar"),
497 }
498}
499
500fn write_number(v: &Value, out: &mut String) {
501 if let Some(i) = v.as_i64() {
502 let mut buf = itoa::Buffer::new();
503 out.push_str(buf.format(i));
504 return;
505 }
506 if let Some(u) = v.as_u64() {
507 let mut buf = itoa::Buffer::new();
508 out.push_str(buf.format(u));
509 return;
510 }
511 if let Some(f) = v.as_f64() {
515 write_float(f, out);
516 } else {
517 out.push_str("null");
518 }
519}
520
521fn write_float(f: f64, out: &mut String) {
522 if !f.is_finite() {
523 out.push_str("null");
524 return;
525 }
526 if f == 0.0 {
527 out.push('0');
528 return;
529 }
530 if f.fract() == 0.0 && f.abs() < 1e16 {
532 let mut buf = itoa::Buffer::new();
533 out.push_str(buf.format(f as i64));
534 return;
535 }
536 let mut buf = ryu::Buffer::new();
542 let s = buf.format_finite(f);
543 if s.as_bytes().contains(&b'e') {
544 write!(out, "{}", f).unwrap();
546 } else {
547 out.push_str(s);
548 }
549}
550
551#[inline]
554fn write_string_value<const DELIM: u8>(s: &str, cfg: &Config, out: &mut String) {
555 if value_needs_quoting::<DELIM>(s, cfg.escape_controls) {
556 write_quoted(s, cfg.escape_controls, out);
557 } else {
558 out.push_str(s);
559 }
560}
561
562fn write_key(k: &str, cfg: &Config, out: &mut String) {
563 if key_needs_quoting(k) {
564 write_quoted(k, cfg.escape_controls, out);
565 } else {
566 out.push_str(k);
567 }
568}
569
570#[inline]
576fn key_needs_quoting(s: &str) -> bool {
577 if s.is_empty() {
578 return true;
579 }
580 let bytes = s.as_bytes();
581 let start = match bytes[0] {
582 b'@' | b'$' | b'#' => {
583 if bytes.len() < 2 {
584 return true; }
586 1
587 }
588 _ => 0,
589 };
590 let first = bytes[start];
591 if !(first.is_ascii_alphabetic() || first == b'_') {
592 return true;
593 }
594 for &b in &bytes[start + 1..] {
595 if !(b.is_ascii_alphanumeric() || b == b'_' || b == b'.') {
596 return true;
597 }
598 }
599 false
600}
601
602#[inline]
603fn value_needs_quoting<const DELIM: u8>(s: &str, escape_controls: bool) -> bool {
604 if s.is_empty() {
605 return true;
606 }
607 let bytes = s.as_bytes();
608 match bytes[0] {
609 b'-' | b'[' | b'{' | b'"' | b'#' | b' ' | b'\t' => return true,
610 _ => {}
611 }
612 match bytes[bytes.len() - 1] {
613 b' ' | b'\t' => return true,
614 _ => {}
615 }
616 for &b in bytes {
620 match b {
621 b':' | b'\n' | b'\r' | b'\t' | b'"' | b'\\' => return true,
622 _ if escape_controls && b < 0x20 => return true,
625 _ if b == DELIM => return true,
626 _ => {}
627 }
628 }
629 if matches!(s, "true" | "false" | "null") {
630 return true;
631 }
632 looks_like_number(bytes)
633}
634
635fn looks_like_number(bytes: &[u8]) -> bool {
636 let mut i = 0;
637 if bytes[0] == b'-' {
638 i = 1;
639 if i == bytes.len() {
640 return false;
641 }
642 }
643 let mut has_digit = false;
644 while i < bytes.len() && bytes[i].is_ascii_digit() {
645 has_digit = true;
646 i += 1;
647 }
648 if !has_digit {
649 return false;
650 }
651 if i < bytes.len() && bytes[i] == b'.' {
652 i += 1;
653 let mut has_frac = false;
654 while i < bytes.len() && bytes[i].is_ascii_digit() {
655 has_frac = true;
656 i += 1;
657 }
658 if !has_frac {
659 return false;
660 }
661 }
662 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
663 i += 1;
664 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
665 i += 1;
666 }
667 let mut has_exp_digit = false;
668 while i < bytes.len() && bytes[i].is_ascii_digit() {
669 has_exp_digit = true;
670 i += 1;
671 }
672 if !has_exp_digit {
673 return false;
674 }
675 }
676 i == bytes.len()
677}
678
679#[inline]
681fn hex_lower(n: u8) -> u8 {
682 match n {
683 0..=9 => b'0' + n,
684 _ => b'a' + (n - 10),
685 }
686}
687
688fn write_quoted(s: &str, escape_controls: bool, out: &mut String) {
689 out.push('"');
690 let bytes = s.as_bytes();
691 let mut start = 0;
692 for (i, &b) in bytes.iter().enumerate() {
693 let named = matches!(b, b'\\' | b'"' | b'\n' | b'\r' | b'\t');
696 let other_control = escape_controls && b < 0x20;
697 if named || other_control {
698 if start < i {
699 out.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) });
700 }
701 match b {
702 b'\\' => out.push_str("\\\\"),
703 b'"' => out.push_str("\\\""),
704 b'\n' => out.push_str("\\n"),
705 b'\r' => out.push_str("\\r"),
706 b'\t' => out.push_str("\\t"),
707 _ => {
708 out.push_str("\\u00");
710 out.push(hex_lower(b >> 4) as char);
711 out.push(hex_lower(b & 0x0f) as char);
712 }
713 }
714 start = i + 1;
715 }
716 }
717 if start < bytes.len() {
718 out.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..]) });
719 }
720 out.push('"');
721}
722
723#[cfg(test)]
724mod tests {
725 use super::{encode, encode_with, Config};
726
727 fn enc(json: &str) -> String {
728 encode(json.as_bytes()).unwrap()
729 }
730
731 fn enc_with(json: &str, cfg: &Config) -> String {
732 encode_with(json.as_bytes(), cfg).unwrap()
733 }
734
735 #[test]
740 fn test_write_number_small_exponent_expands_to_decimal() {
741 assert_eq!(enc(r#"{"n":1e-6}"#), "n: 0.000001");
742 assert_eq!(enc(r#"{"n":1e-7}"#), "n: 0.0000001");
743 }
744
745 #[test]
746 fn test_write_number_integer_valued_float_drops_fraction() {
747 assert_eq!(enc(r#"{"n":100.0}"#), "n: 100");
748 assert_eq!(enc(r#"{"n":-2.0}"#), "n: -2");
749 }
750
751 #[test]
752 fn test_write_number_decimal_full_precision_preserved() {
753 assert_eq!(enc(r#"{"n":3.14}"#), "n: 3.14");
754 assert_eq!(enc(r#"{"n":0.3333333333333333}"#), "n: 0.3333333333333333");
755 assert_eq!(enc(r#"{"n":1234567.89}"#), "n: 1234567.89");
756 }
757
758 #[test]
759 fn test_write_number_large_magnitude_float_expands_no_exponent() {
760 assert_eq!(enc(r#"{"n":1e21}"#), "n: 1000000000000000000000");
761 }
762
763 #[test]
764 fn test_write_number_i64_and_u64_fast_paths() {
765 assert_eq!(enc(r#"{"n":42}"#), "n: 42");
766 assert_eq!(
767 enc(r#"{"n":-9223372036854775808}"#),
768 "n: -9223372036854775808"
769 );
770 assert_eq!(
771 enc(r#"{"n":18446744073709551615}"#),
772 "n: 18446744073709551615"
773 );
774 }
775
776 #[test]
777 fn test_write_number_beyond_u64_keeps_expanded_form() {
778 assert_eq!(enc(r#"{"n":1e30}"#), "n: 1000000000000000000000000000000");
781 }
782
783 #[test]
786 fn test_empty_array_root_is_bare_brackets() {
787 assert_eq!(enc("[]"), "[]");
788 }
789
790 #[test]
791 fn test_empty_array_object_field_is_key_bracket() {
792 assert_eq!(enc(r#"{"a":[]}"#), "a: []");
793 assert_eq!(enc(r#"{"x":{"a":[]}}"#), "x:\n a: []");
794 }
795
796 #[test]
797 fn test_empty_array_as_array_element_keeps_legacy_header() {
798 assert_eq!(enc(r#"{"pairs":[[],[]]}"#), "pairs[2]:\n - [0]:\n - [0]:");
800 }
801
802 #[test]
803 fn test_empty_array_legacy_form_when_option_off() {
804 let cfg = Config {
805 empty_array_bare: false,
806 ..Config::default()
807 };
808 assert_eq!(enc_with("[]", &cfg), "[0]:");
809 assert_eq!(enc_with(r#"{"a":[]}"#, &cfg), "a[0]:");
810 }
811
812 #[test]
818 fn test_escape_controls_emits_lowercase_u_escape() {
819 assert_eq!(enc("{\"s\":\"a\\u001fb\"}"), "s: \"a\\u001fb\"");
823 assert_eq!(enc("{\"s\":\"a\\u0000b\"}"), "s: \"a\\u0000b\"");
824 assert_eq!(enc("{\"s\":\"\\u0004\"}"), "s: \"\\u0004\"");
825 }
826
827 #[test]
828 fn test_escape_controls_keeps_named_escapes() {
829 assert_eq!(enc(r#"{"s":"a\nb"}"#), "s: \"a\\nb\"");
830 assert_eq!(enc(r#"{"s":"a\tb"}"#), "s: \"a\\tb\"");
831 assert_eq!(enc(r#"{"s":"a\rb"}"#), "s: \"a\\rb\"");
832 }
833
834 #[test]
835 fn test_escape_controls_off_passes_raw_byte() {
836 let cfg = Config {
837 escape_controls: false,
838 ..Config::default()
839 };
840 assert_eq!(enc_with("{\"s\":\"a\\u001fb\"}", &cfg), "s: a\u{1f}b");
841 }
842
843 #[test]
846 fn test_fold_keys_root_chain() {
847 let cfg = Config {
848 key_folding: true,
849 ..Config::default()
850 };
851 assert_eq!(enc_with(r#"{"a":{"b":{"c":1}}}"#, &cfg), "a.b.c: 1");
852 }
853
854 #[test]
855 fn test_fold_keys_restarts_in_multikey_object_body() {
856 let cfg = Config {
859 key_folding: true,
860 ..Config::default()
861 };
862 assert_eq!(
863 enc_with(r#"{"a":{"x":1,"nested":{"b":{"c":2}}}}"#, &cfg),
864 "a:\n x: 1\n nested.b.c: 2"
865 );
866 }
867
868 #[test]
869 fn test_fold_keys_does_not_refold_past_flatten_depth() {
870 let cfg = Config {
871 key_folding: true,
872 flatten_depth: Some(2),
873 ..Config::default()
874 };
875 assert_eq!(
876 enc_with(r#"{"a":{"b":{"c":{"d":1}}}}"#, &cfg),
877 "a.b:\n c:\n d: 1"
878 );
879 }
880
881 #[test]
882 fn test_fold_keys_skips_sibling_collision_at_any_depth() {
883 let cfg = Config {
885 key_folding: true,
886 ..Config::default()
887 };
888 assert_eq!(
889 enc_with(
890 r#"{"data":{"meta":{"items":[1,2]}},"data.meta.items":"literal"}"#,
891 &cfg
892 ),
893 "data:\n meta:\n items[2]: 1,2\ndata.meta.items: literal"
894 );
895 }
896
897 #[test]
900 fn test_max_depth_rejects_overdeep_input_before_parse() {
901 let deep: Vec<u8> = b"["
904 .iter()
905 .cycle()
906 .take(100)
907 .chain(b"1".iter())
908 .chain(b"]".iter().cycle().take(100))
909 .copied()
910 .collect();
911 let cfg = Config {
912 max_depth: 10,
913 ..Config::default()
914 };
915 let err = encode_with(&deep, &cfg).unwrap_err();
916 assert!(err.contains("max_depth"), "got: {err}");
917 }
918
919 #[test]
920 fn test_max_depth_default_allows_normal_nesting() {
921 assert_eq!(enc(r#"{"a":{"b":{"c":1}}}"#), "a:\n b:\n c: 1");
923 }
924
925 #[test]
926 fn test_max_depth_ignores_brackets_inside_strings() {
927 let cfg = Config {
929 max_depth: 2,
930 ..Config::default()
931 };
932 assert_eq!(
933 enc_with(r#"{"s":"[[[[[deep]]]]]"}"#, &cfg),
934 r#"s: "[[[[[deep]]]]]""#
935 );
936 }
937
938 #[test]
941 fn test_max_input_bytes_rejects_oversize_input() {
942 let cfg = Config {
943 max_input_bytes: 4,
944 ..Config::default()
945 };
946 let err = encode_with(br#"{"a":1}"#, &cfg).unwrap_err();
947 assert!(err.contains("max_input_bytes"), "got: {err}");
948 }
949
950 #[test]
951 fn test_max_input_bytes_zero_disables_check() {
952 assert_eq!(enc(r#"{"a":1}"#), "a: 1");
954 }
955
956 #[test]
959 fn test_ryu_regular_floats_match_spec_form() {
960 assert_eq!(enc(r#"{"n":2.5}"#), "n: 2.5");
962 assert_eq!(enc(r#"{"n":99.99}"#), "n: 99.99");
963 assert_eq!(enc(r#"{"n":0.1}"#), "n: 0.1");
964 assert_eq!(enc(r#"{"n":-0.0625}"#), "n: -0.0625");
965 }
966}