1use std::collections::HashMap;
43
44pub const MAX_DEPTH: usize = 10_000;
48
49use crate::console::{Console, ConsoleOptions};
50use crate::errors::{Result, RichError};
51use crate::measure::Measurement;
52use crate::protocol::Renderable;
53use crate::segment::Segment;
54use crate::style::StyleType;
55use crate::text::{Span, Text};
56
57pub struct Json {
63 value: Node,
64 options: JsonOptions,
65 lines: std::sync::OnceLock<Vec<Text>>,
71 no_wrap: bool,
73 #[cfg(feature = "json-escape-safe")]
74 escape_safe: bool,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct JsonOptions {
85 pub indent: Option<String>,
88 pub highlight: bool,
90 pub skip_keys: bool,
92 pub ensure_ascii: bool,
94 pub check_circular: bool,
96 pub allow_nan: bool,
99 pub sort_keys: bool,
101}
102
103impl Default for JsonOptions {
104 fn default() -> Self {
105 JsonOptions {
106 indent: Some(" ".to_string()),
107 highlight: true,
108 skip_keys: false,
109 ensure_ascii: false,
110 check_circular: true,
111 allow_nan: true,
112 sort_keys: false,
113 }
114 }
115}
116
117impl JsonOptions {
118 pub fn indent_spaces(indent: i64) -> Option<String> {
122 Some(" ".repeat(usize::try_from(indent).unwrap_or(0)))
123 }
124}
125
126type PyStr = Vec<u8>;
131
132#[derive(Debug)]
138enum Node {
139 Null,
140 Bool(bool),
141 Number(String),
142 NonFinite(&'static str),
146 Str(PyStr),
147 Array(Vec<Node>),
148 Object(Vec<(PyStr, Node)>),
149}
150
151impl Drop for Node {
152 fn drop(&mut self) {
159 let mut pending: Vec<Node> = Vec::new();
160 take_children(self, &mut pending);
161 while let Some(mut node) = pending.pop() {
162 take_children(&mut node, &mut pending);
163 }
166 }
167}
168
169fn take_children(node: &mut Node, out: &mut Vec<Node>) {
171 match node {
172 Node::Array(items) => out.append(items),
173 Node::Object(entries) => out.extend(entries.drain(..).map(|(_, value)| value)),
174 _ => {}
175 }
176}
177
178impl Json {
179 pub fn new(text: &str) -> Result<Self> {
182 Json::with_options(text, &JsonOptions::default())
183 }
184
185 pub fn from_data(data: &serde_json::Value, options: &JsonOptions) -> Result<Self> {
189 let text = serde_json::to_string(data).map_err(|e| RichError::Json(e.to_string()))?;
190 Json::with_options(&text, options)
191 }
192
193 pub fn with_options(text: &str, options: &JsonOptions) -> Result<Self> {
196 let value = Parser::new(text).parse_document()?;
197 if !options.allow_nan
199 && let Some(literal) = first_non_finite(&value)
200 {
201 return Err(RichError::Json(format!(
202 "Out of range float values are not JSON compliant: {}",
203 match literal {
204 "NaN" => "nan",
205 "Infinity" => "inf",
206 _ => "-inf",
207 }
208 )));
209 }
210 Ok(Json {
211 value,
212 options: options.clone(),
213 lines: std::sync::OnceLock::new(),
214 no_wrap: false,
215 #[cfg(feature = "json-escape-safe")]
216 escape_safe: false,
217 })
218 }
219
220 fn lines(&self) -> &[Text] {
222 self.lines.get_or_init(|| {
223 let dumped = dumps(&self.value, &self.options);
224 let text = if !self.options.highlight {
225 Text::new(dumped.json)
226 } else if dumped.exact {
227 let mut text = Text::new(dumped.json);
228 let mut spans = dumped.spans;
229 spans.extend(dumped.keys);
230 text.set_spans(spans);
231 text
232 } else {
233 json_highlight(dumped.json)
234 };
235 if text.plain().contains('\n') {
236 text.split("\n", false, true)
237 } else {
238 vec![text]
239 }
240 })
241 }
242
243 pub fn text(&self) -> Text {
246 match self.lines() {
247 [line] => line.clone(),
248 lines => Text::new("\n").join(lines),
249 }
250 }
251
252 #[cfg(feature = "json-escape-safe")]
257 pub fn escape_safe(mut self, enabled: bool) -> Self {
258 self.escape_safe = enabled;
259 self
260 }
261
262 #[must_use]
290 pub fn no_wrap(mut self, no_wrap: bool) -> Self {
291 self.no_wrap = no_wrap;
292 self
293 }
294
295 #[cfg(feature = "json-escape-safe")]
297 fn render_unwrapped(&self, console: &Console) -> Vec<Segment> {
298 let mut out = Vec::new();
299 for (index, line) in self.lines().iter().enumerate() {
300 if index > 0 {
301 out.push(Segment::line());
302 }
303 out.extend(line.render(console.theme(), console.base_style()));
304 }
305 out
306 }
307}
308
309struct Dumped {
311 json: String,
312 spans: Vec<Span>,
314 keys: Vec<Span>,
316 exact: bool,
320}
321
322impl Dumped {
323 fn styled(&mut self, token: &str, style: &'static str) {
324 let start = self.json.len();
325 self.json.push_str(token);
326 self.spans.push(Span {
327 start,
328 end: self.json.len(),
329 style: StyleType::Name(style.to_string()),
330 });
331 }
332
333 fn string(&mut self, quoted: &str, key: bool) {
337 if quoted.as_bytes().get(quoted.len().wrapping_sub(2)) == Some(&b'\\') {
338 self.exact = false;
339 }
340 let start = self.json.len();
341 self.styled(quoted, "json.str");
342 if key {
343 self.keys.push(Span {
344 start,
345 end: self.json.len(),
346 style: StyleType::Name("json.key".to_string()),
347 });
348 }
349 }
350}
351
352fn dumps(value: &Node, options: &JsonOptions) -> Dumped {
355 use std::borrow::Cow;
356 let indent = options.indent.as_deref();
357 let item_separator = if indent.is_some() { "," } else { ", " };
360 let newline_indent = |level: usize| -> Cow<'static, str> {
361 match indent {
362 Some(indent) => Cow::Owned(format!("\n{}", indent.repeat(level))),
363 None => Cow::Borrowed(""),
364 }
365 };
366 let quote_string = |string: &[u8]| -> String { quote_py(string, options.ensure_ascii) };
367
368 enum Task<'a> {
370 Value(&'a Node, usize),
372 Plain(Cow<'a, str>),
374 Close(&'static str),
376 Key(&'a [u8]),
378 }
379
380 let mut dumped = Dumped {
381 json: String::new(),
382 spans: Vec::new(),
383 keys: Vec::new(),
384 exact: indent.is_none_or(|indent| indent.chars().all(|c| c == ' ' || c == '\t')),
386 };
387 let mut stack = vec![Task::Value(value, 0)];
388 while let Some(task) = stack.pop() {
389 let (node, level) = match task {
390 Task::Plain(text) => {
391 dumped.json.push_str(&text);
392 continue;
393 }
394 Task::Close(brace) => {
395 dumped.styled(brace, "json.brace");
396 continue;
397 }
398 Task::Key(key) => {
399 dumped.string("e_string(key), true);
400 dumped.json.push_str(": ");
401 continue;
402 }
403 Task::Value(node, level) => (node, level),
404 };
405 match node {
406 Node::Null => dumped.styled("null", "json.null"),
407 Node::Bool(true) => dumped.styled("true", "json.bool_true"),
408 Node::Bool(false) => dumped.styled("false", "json.bool_false"),
409 Node::Number(number) => dumped.styled(number, "json.number"),
410 Node::NonFinite(literal) => dumped.json.push_str(literal),
412 Node::Str(string) => dumped.string("e_string(string), false),
413 Node::Array(items) => {
414 dumped.styled("[", "json.brace");
415 if items.is_empty() {
416 dumped.styled("]", "json.brace");
417 continue;
418 }
419 stack.push(Task::Close("]"));
421 stack.push(Task::Plain(newline_indent(level)));
422 let last = items.len() - 1;
423 for (index, item) in items.iter().enumerate().rev() {
424 if index != last {
425 stack.push(Task::Plain(Cow::Borrowed(item_separator)));
426 }
427 stack.push(Task::Value(item, level + 1));
428 stack.push(Task::Plain(newline_indent(level + 1)));
429 }
430 }
431 Node::Object(entries) => {
432 dumped.styled("{", "json.brace");
433 if entries.is_empty() {
434 dumped.styled("}", "json.brace");
435 continue;
436 }
437 let mut order: Vec<&(PyStr, Node)> = entries.iter().collect();
438 if options.sort_keys {
439 order.sort_by(|a, b| a.0.cmp(&b.0));
442 }
443 stack.push(Task::Close("}"));
444 stack.push(Task::Plain(newline_indent(level)));
445 let last = order.len() - 1;
446 for (index, (key, item)) in order.into_iter().enumerate().rev() {
447 if index != last {
448 stack.push(Task::Plain(Cow::Borrowed(item_separator)));
449 }
450 stack.push(Task::Value(item, level + 1));
451 stack.push(Task::Key(key));
452 stack.push(Task::Plain(newline_indent(level + 1)));
453 }
454 }
455 }
456 }
457 dumped
458}
459
460fn first_non_finite(value: &Node) -> Option<&'static str> {
462 let mut pending = vec![value];
463 while let Some(node) = pending.pop() {
464 match node {
465 Node::NonFinite(literal) => return Some(literal),
466 Node::Array(items) => pending.extend(items.iter()),
467 Node::Object(entries) => pending.extend(entries.iter().map(|(_, value)| value)),
468 _ => {}
469 }
470 }
471 None
472}
473
474fn ascii_escape(quoted: &str) -> String {
479 if quoted.bytes().all(|b| (b' '..=b'~').contains(&b)) {
480 return quoted.to_string();
481 }
482 let mut out = String::with_capacity(quoted.len() + 16);
483 for c in quoted.chars() {
484 if (' '..='~').contains(&c) {
485 out.push(c);
486 continue;
487 }
488 let mut units = [0u16; 2];
489 for unit in c.encode_utf16(&mut units) {
490 out.push_str(&format!("\\u{unit:04x}"));
491 }
492 }
493 out
494}
495
496fn json_patterns() -> &'static (fancy_regex::Regex, fancy_regex::Regex) {
499 static PATTERNS: std::sync::OnceLock<(fancy_regex::Regex, fancy_regex::Regex)> =
500 std::sync::OnceLock::new();
501 PATTERNS.get_or_init(|| {
502 const JSON_STR: &str = r#"(?<![\\\w])(?P<str>b?".*?(?<!\\)")"#;
503 let combined = [
504 r"(?P<brace>[\{\[\(\)\]\}])",
505 r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
506 r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
507 JSON_STR,
508 ]
509 .join("|");
510 (
511 fancy_regex::Regex::new(&combined).expect("valid JSON highlighter pattern"),
512 fancy_regex::Regex::new(JSON_STR).expect("valid JSON string pattern"),
513 )
514 })
515}
516
517fn json_highlight(json: String) -> Text {
521 let (combined, string) = json_patterns();
522 let mut text = Text::new(json);
523 text.highlight_with_regex(combined, None, "json.");
524 let plain = text.plain();
525 let mut keys = Vec::new();
526 for found in string.find_iter(plain) {
527 let Ok(found) = found else { break };
528 for &byte in &plain.as_bytes()[found.end()..] {
529 match byte {
530 b':' => keys.push((found.start(), found.end())),
531 b' ' | b'\n' | b'\r' | b'\t' => continue,
532 _ => {}
533 }
534 break;
535 }
536 }
537 for (start, end) in keys {
538 text.push_span(Span {
539 start,
540 end,
541 style: StyleType::Name("json.key".to_string()),
542 });
543 }
544 text
545}
546
547impl Renderable for Json {
548 fn measure(&self, _console: &Console, _options: &ConsoleOptions) -> Measurement {
551 let mut minimum = 0;
552 let mut maximum = 0;
553 for line in self.lines() {
554 let (line_minimum, line_maximum) = line.measurement();
555 minimum = minimum.max(line_minimum);
556 maximum = maximum.max(line_maximum);
557 }
558 Measurement::new(minimum, maximum)
559 }
560
561 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
565 #[cfg(feature = "json-escape-safe")]
566 if self.escape_safe {
567 let segments = self.render_unwrapped(console);
568 return escape_safe_lines(&segments, options.max_width, self.no_wrap);
569 }
570 let no_wrap = self.no_wrap || options.no_wrap.unwrap_or(false);
571 let overflow = options.overflow.unwrap_or_default();
572 let tab_size = match console.tab_size() {
573 0 => crate::text::DEFAULT_TAB_SIZE,
574 tab_size => tab_size,
575 };
576 let mut out = Vec::new();
577 let mut first = true;
578 for line in self.lines() {
579 for rendered in line.render_lines_wrapped_tabs(
580 console.theme(),
581 console.base_style(),
582 Some(options.max_width),
583 options.justify,
584 overflow,
585 no_wrap,
586 tab_size,
587 ) {
588 if !first {
589 out.push(Segment::line());
590 }
591 first = false;
592 out.extend(rendered);
593 }
594 }
595 out
596 }
597}
598
599#[cfg(feature = "json-escape-safe")]
603fn escape_safe_lines(segments: &[Segment], width: usize, crop: bool) -> Vec<Segment> {
604 if width == 0 {
605 return Vec::new();
606 }
607 let lines = Segment::split_lines(segments);
608 let last = lines.len().saturating_sub(1);
609 let mut out = Vec::new();
610 for (line_index, line) in lines.into_iter().enumerate() {
611 let plain: String = line
612 .iter()
613 .filter(|s| !s.control)
614 .map(|s| s.text.as_str())
615 .collect();
616 if !plain.contains('\\') {
618 out.extend(if crop {
619 Segment::crop_lines(&line, width)
620 } else {
621 Segment::fold_lines_words(&line, width)
622 });
623 } else {
624 let (spans, _) = crate::cells::split_graphemes(&plain);
625 let mut atoms = Vec::new();
626 let mut index = 0;
627 while index < spans.len() {
628 let (start, mut end, mut cells) = spans[index];
629 if plain.as_bytes()[start] == b'\\' {
630 let escape_end = start
631 + if plain.as_bytes().get(start + 1) == Some(&b'u') {
632 6
633 } else {
634 2
635 };
636 while end < escape_end && index + 1 < spans.len() {
637 index += 1;
638 end = spans[index].1;
639 cells += spans[index].2;
640 }
641 if !crop && cells > width {
642 for offset in start..escape_end {
646 atoms.push((offset, offset + 1, 1));
647 }
648 if end > escape_end {
649 atoms.push((escape_end, end, 0));
650 }
651 index += 1;
652 continue;
653 }
654 }
655 atoms.push((start, end, cells));
656 index += 1;
657 }
658 let mut breaks = Vec::new();
659 let mut cells = 0;
660 let mut stop = plain.len();
661 for (start, _, atom_width) in atoms {
662 if cells + atom_width > width {
663 if crop {
664 stop = start;
665 break;
666 }
667 if cells > 0 {
668 breaks.push(start);
669 cells = 0;
670 }
671 }
672 cells += atom_width;
673 }
674 let mut position = 0;
675 let mut next = 0;
676 for segment in line {
677 if segment.control {
678 out.push(segment);
679 continue;
680 }
681 let mut buffer = String::new();
682 for ch in segment.text.chars() {
683 if position >= stop {
684 break;
685 }
686 if breaks.get(next) == Some(&position) {
687 if !buffer.is_empty() {
688 out.push(Segment::new(
689 std::mem::take(&mut buffer),
690 segment.style.clone(),
691 ));
692 }
693 out.push(Segment::line());
694 next += 1;
695 }
696 buffer.push(ch);
697 position += ch.len_utf8();
698 }
699 if !buffer.is_empty() {
700 out.push(Segment::new(buffer, segment.style));
701 }
702 }
703 }
704 if line_index != last {
705 out.push(Segment::line());
706 }
707 }
708 out
709}
710
711fn quote_py(string: &[u8], ensure_ascii: bool) -> String {
716 let escape = |run: &str| -> String {
717 let quoted = quote(run);
718 if ensure_ascii {
719 ascii_escape("ed)
720 } else {
721 quoted
722 }
723 };
724 if let Ok(run) = std::str::from_utf8(string) {
725 return escape(run);
726 }
727 let mut out = String::from("\"");
728 let mut rest = string;
729 while !rest.is_empty() {
730 let valid = match std::str::from_utf8(rest) {
731 Ok(_) => rest.len(),
732 Err(error) => error.valid_up_to(),
733 };
734 let (run, tail) = rest.split_at(valid);
735 let quoted = escape(std::str::from_utf8(run).unwrap_or_default());
736 out.push_str("ed[1..quoted.len() - 1]);
737 rest = tail;
738 if let [0xED, b1 @ 0xA0..=0xBF, b2, tail @ ..] = rest {
739 let unit = 0xD000 | (u32::from(b1 & 0x3F) << 6) | u32::from(b2 & 0x3F);
740 if ensure_ascii {
741 out.push_str(&format!("\\u{unit:04x}"));
742 } else {
743 out.push(char::REPLACEMENT_CHARACTER);
744 }
745 rest = tail;
746 } else if !rest.is_empty() {
747 out.push(char::REPLACEMENT_CHARACTER);
749 rest = &rest[1..];
750 }
751 }
752 out.push('"');
753 out
754}
755
756fn decode_with_surrogates(token: &str) -> Option<PyStr> {
761 let inner = token.strip_prefix('"')?.strip_suffix('"')?;
762 let mut out = Vec::with_capacity(inner.len());
763 let mut chars = inner.chars().peekable();
764 let hex4 = |chars: &mut std::iter::Peekable<std::str::Chars<'_>>| -> Option<u32> {
765 let mut value = 0;
766 for _ in 0..4 {
767 value = value * 16 + chars.next()?.to_digit(16)?;
768 }
769 Some(value)
770 };
771 while let Some(c) = chars.next() {
772 if c < ' ' {
773 return None;
774 }
775 if c != '\\' {
776 let mut buf = [0u8; 4];
777 out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
778 continue;
779 }
780 let decoded = match chars.next()? {
781 '"' => '"',
782 '\\' => '\\',
783 '/' => '/',
784 'b' => '\u{8}',
785 'f' => '\u{c}',
786 'n' => '\n',
787 'r' => '\r',
788 't' => '\t',
789 'u' => {
790 let mut unit = hex4(&mut chars)?;
791 if (0xD800..0xDC00).contains(&unit) {
792 let mut lookahead = chars.clone();
793 if lookahead.next() == Some('\\')
794 && lookahead.next() == Some('u')
795 && let Some(low @ 0xDC00..=0xDFFF) = hex4(&mut lookahead)
796 {
797 unit = 0x10000 + ((unit - 0xD800) << 10) + (low - 0xDC00);
798 chars = lookahead;
799 }
800 }
801 match char::from_u32(unit) {
802 Some(c) => c,
803 None => {
804 out.push(0xE0 | (unit >> 12) as u8);
806 out.push(0x80 | ((unit >> 6) & 0x3F) as u8);
807 out.push(0x80 | (unit & 0x3F) as u8);
808 continue;
809 }
810 }
811 }
812 _ => return None,
813 };
814 let mut buf = [0u8; 4];
815 out.extend_from_slice(decoded.encode_utf8(&mut buf).as_bytes());
816 }
817 Some(out)
818}
819
820fn quote(string: &str) -> String {
821 serde_json::to_string(string).unwrap_or_else(|_| format!("{string:?}"))
822}
823
824enum Frame {
826 Array(Vec<Node>),
827 Object {
828 entries: Vec<(PyStr, Node)>,
829 seen: HashMap<PyStr, usize>,
833 key: PyStr,
835 },
836}
837
838struct Parser<'a> {
842 src: &'a str,
843 bytes: &'a [u8],
844 pos: usize,
845}
846
847impl<'a> Parser<'a> {
848 fn new(src: &'a str) -> Self {
849 Parser {
850 src,
851 bytes: src.as_bytes(),
852 pos: 0,
853 }
854 }
855
856 fn parse_document(&mut self) -> Result<Node> {
857 let value = self.parse_value()?;
858 self.skip_whitespace();
859 if self.pos != self.bytes.len() {
860 return Err(self.error("trailing characters"));
861 }
862 Ok(value)
863 }
864
865 fn parse_value(&mut self) -> Result<Node> {
867 let mut stack: Vec<Frame> = Vec::new();
868 let mut node: Node;
869
870 'descend: loop {
871 self.skip_whitespace();
872 if let Some(kind @ (b'[' | b'{')) = self.peek()
875 && stack.len() >= MAX_DEPTH
876 {
877 let what = if kind == b'[' { "array" } else { "object" };
878 return Err(self.error(&format!(
879 "maximum recursion depth exceeded while decoding a JSON {what} \
880 from a unicode string"
881 )));
882 }
883 match self.peek() {
884 Some(b'[') => {
885 self.pos += 1;
886 self.skip_whitespace();
887 if self.peek() == Some(b']') {
888 self.pos += 1;
889 node = Node::Array(Vec::new());
890 } else {
891 stack.push(Frame::Array(Vec::new()));
892 continue 'descend;
893 }
894 }
895 Some(b'{') => {
896 self.pos += 1;
897 self.skip_whitespace();
898 if self.peek() == Some(b'}') {
899 self.pos += 1;
900 node = Node::Object(Vec::new());
901 } else {
902 let key = self.parse_key()?;
903 stack.push(Frame::Object {
904 entries: Vec::new(),
905 seen: HashMap::new(),
906 key,
907 });
908 continue 'descend;
909 }
910 }
911 _ => node = self.parse_scalar()?,
912 }
913
914 loop {
917 let Some(frame) = stack.last_mut() else {
918 return Ok(node);
919 };
920 let closing = match frame {
921 Frame::Array(items) => {
922 items.push(node);
923 b']'
924 }
925 Frame::Object { entries, seen, key } => {
926 let key = std::mem::take(key);
927 match seen.get(&key) {
928 Some(&at) => entries[at].1 = node,
929 None => {
930 seen.insert(key.clone(), entries.len());
931 entries.push((key, node));
932 }
933 }
934 b'}'
935 }
936 };
937 self.skip_whitespace();
938 match self.peek() {
939 Some(b',') => {
940 self.pos += 1;
941 if closing == b'}' {
942 let next_key = self.parse_key()?;
943 if let Some(Frame::Object { key, .. }) = stack.last_mut() {
944 *key = next_key;
945 }
946 }
947 continue 'descend;
948 }
949 Some(byte) if byte == closing => {
950 self.pos += 1;
951 node = match stack.pop() {
952 Some(Frame::Array(items)) => Node::Array(items),
953 Some(Frame::Object { entries, .. }) => Node::Object(entries),
954 None => unreachable!("the frame was just borrowed"),
955 };
956 }
957 _ if closing == b']' => return Err(self.error("expected `,` or `]`")),
958 _ => return Err(self.error("expected `,` or `}`")),
959 }
960 }
961 }
962 }
963
964 fn parse_key(&mut self) -> Result<PyStr> {
966 self.skip_whitespace();
967 if self.peek() != Some(b'"') {
968 return Err(self.error("key must be a string"));
969 }
970 let key = self.parse_string()?;
971 self.skip_whitespace();
972 if self.peek() != Some(b':') {
973 return Err(self.error("expected `:`"));
974 }
975 self.pos += 1;
976 Ok(key)
977 }
978
979 fn parse_scalar(&mut self) -> Result<Node> {
980 match self.peek() {
981 Some(b'"') => Ok(Node::Str(self.parse_string()?)),
982 Some(b't') => {
983 self.expect_literal("true")?;
984 Ok(Node::Bool(true))
985 }
986 Some(b'f') => {
987 self.expect_literal("false")?;
988 Ok(Node::Bool(false))
989 }
990 Some(b'n') => {
991 self.expect_literal("null")?;
992 Ok(Node::Null)
993 }
994 Some(b'N') => {
998 self.expect_literal("NaN")?;
999 Ok(Node::NonFinite("NaN"))
1000 }
1001 Some(b'I') => {
1002 self.expect_literal("Infinity")?;
1003 Ok(Node::NonFinite("Infinity"))
1004 }
1005 Some(b'-') if self.src[self.pos..].starts_with("-Infinity") => {
1006 self.pos += "-Infinity".len();
1007 Ok(Node::NonFinite("-Infinity"))
1008 }
1009 Some(b'-' | b'0'..=b'9') => self.parse_number(),
1010 Some(_) => Err(self.error("expected value")),
1011 None => Err(self.error("EOF while parsing a value")),
1012 }
1013 }
1014
1015 fn parse_string(&mut self) -> Result<PyStr> {
1018 let start = self.pos;
1019 let mut end = self.pos + 1;
1020 loop {
1021 match self.bytes.get(end) {
1022 None => return Err(self.error_at(self.bytes.len(), "EOF while parsing a string")),
1023 Some(b'"') => {
1024 end += 1;
1025 break;
1026 }
1027 Some(b'\\') => {
1028 end += 1;
1029 match self.src[end..].chars().next() {
1034 Some(ch) => end += ch.len_utf8(),
1035 None => {
1036 return Err(
1037 self.error_at(self.bytes.len(), "EOF while parsing a string")
1038 );
1039 }
1040 }
1041 }
1042 Some(_) => end += 1,
1045 }
1046 }
1047 let token = &self.src[start..end];
1048 let decoded = match serde_json::from_str::<String>(token) {
1049 Ok(decoded) => decoded.into_bytes(),
1050 Err(error) => decode_with_surrogates(token)
1053 .ok_or_else(|| self.error_at(start, &describe(&error)))?,
1054 };
1055 self.pos = end;
1056 Ok(decoded)
1057 }
1058
1059 fn parse_number(&mut self) -> Result<Node> {
1062 let start = self.pos;
1063 let mut end = start;
1064 while matches!(
1065 self.bytes.get(end),
1066 Some(b'-' | b'+' | b'.' | b'e' | b'E' | b'0'..=b'9')
1067 ) {
1068 end += 1;
1069 }
1070 let token = &self.src[start..end];
1071 let digits = token.as_bytes();
1072 let mut i = usize::from(digits.first() == Some(&b'-'));
1073 if digits.get(i) == Some(&b'0') {
1074 i += 1;
1075 } else {
1076 let first = i;
1077 while digits.get(i).is_some_and(u8::is_ascii_digit) {
1078 i += 1;
1079 }
1080 if i == first {
1081 return Err(self.error_at(start, "invalid number"));
1082 }
1083 }
1084 let mut floating = false;
1085 if digits.get(i) == Some(&b'.') {
1086 floating = true;
1087 i += 1;
1088 let first = i;
1089 while digits.get(i).is_some_and(u8::is_ascii_digit) {
1090 i += 1;
1091 }
1092 if i == first {
1093 return Err(self.error_at(start, "invalid number"));
1094 }
1095 }
1096 if matches!(digits.get(i), Some(b'e' | b'E')) {
1097 floating = true;
1098 i += 1;
1099 if matches!(digits.get(i), Some(b'+' | b'-')) {
1100 i += 1;
1101 }
1102 let first = i;
1103 while digits.get(i).is_some_and(u8::is_ascii_digit) {
1104 i += 1;
1105 }
1106 if i == first {
1107 return Err(self.error_at(start, "invalid number"));
1108 }
1109 }
1110 if i != digits.len() {
1111 return Err(self.error_at(start, "invalid number"));
1112 }
1113 self.pos = end;
1114 if !floating {
1115 return Ok(Node::Number(
1116 if token == "-0" { "0" } else { token }.to_string(),
1117 ));
1118 }
1119 let value: f64 = token
1120 .parse()
1121 .map_err(|_| self.error_at(start, "invalid number"))?;
1122 if value.is_infinite() {
1123 return Ok(Node::NonFinite(if value.is_sign_negative() {
1124 "-Infinity"
1125 } else {
1126 "Infinity"
1127 }));
1128 }
1129 Ok(Node::Number(crate::pyformat::float_repr(value)))
1131 }
1132
1133 fn expect_literal(&mut self, literal: &str) -> Result<()> {
1134 if self.src[self.pos..].starts_with(literal) {
1135 self.pos += literal.len();
1136 Ok(())
1137 } else {
1138 Err(self.error("expected value"))
1139 }
1140 }
1141
1142 fn peek(&self) -> Option<u8> {
1143 self.bytes.get(self.pos).copied()
1144 }
1145
1146 fn skip_whitespace(&mut self) {
1147 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
1148 self.pos += 1;
1149 }
1150 }
1151
1152 fn error(&self, message: &str) -> RichError {
1153 self.error_at(self.pos, message)
1154 }
1155
1156 fn error_at(&self, pos: usize, message: &str) -> RichError {
1157 let (line, column) = self.line_column(pos);
1158 RichError::Json(format!("{message} at line {line} column {column}"))
1159 }
1160
1161 fn line_column(&self, pos: usize) -> (usize, usize) {
1162 let mut pos = pos.min(self.src.len());
1163 while !self.src.is_char_boundary(pos) {
1164 pos -= 1;
1165 }
1166 let before = &self.src[..pos];
1167 let line = 1 + before.matches('\n').count();
1168 let column = before
1169 .rsplit('\n')
1170 .next()
1171 .map_or(0, |tail| tail.chars().count())
1172 + 1;
1173 (line, column)
1174 }
1175}
1176
1177fn describe(error: &serde_json::Error) -> String {
1180 let text = error.to_string();
1181 match text.find(" at line ") {
1182 Some(at) => text[..at].to_string(),
1183 None => text,
1184 }
1185}
1186
1187#[cfg(test)]
1188mod tests {
1189 use super::*;
1190 use crate::color::ColorSystem;
1191
1192 #[test]
1195 fn recorded_spans_match_the_highlighter_regexes() {
1196 let documents = [
1197 r#"{"name": "Alice", "age": 30, "admin": true, "tags": ["a", "b"], "meta": null}"#,
1198 r#"[1e20, -0.0, 1.5e-7, 12345678901234567890, false, [], {}, [[]], {"a": {}}]"#,
1199 r#"{"true": "false", "x:y": "a\"b", "q\"": ": null", "n": [NaN, Infinity, -Infinity]}"#,
1200 r#"{"caf\u00e9": "\u2764 \ud83d\ude00", "ctl": "\u0001\t\n", "": ""}"#,
1201 r#"["[{(1)}]", "0x1F", "b\"x", "\\\"", "tail\\x"]"#,
1202 ];
1203 let indents = [None, Some(" "), Some("\t"), Some("")];
1204 for document in documents {
1205 for indent in indents {
1206 for ensure_ascii in [false, true] {
1207 let options = JsonOptions {
1208 indent: indent.map(str::to_string),
1209 ensure_ascii,
1210 sort_keys: true,
1211 ..JsonOptions::default()
1212 };
1213 let value = Parser::new(document).parse_document().unwrap();
1214 let dumped = dumps(&value, &options);
1215 assert!(dumped.exact, "{document} / {indent:?}");
1216 let mut recorded = dumped.spans.clone();
1217 recorded.extend(dumped.keys.clone());
1218 let expected = json_highlight(dumped.json.clone());
1219 assert_eq!(recorded, expected.spans(), "{document} / {indent:?}");
1220 }
1221 }
1222 }
1223 let value = Parser::new(r#"{"k": "a\\"}"#).parse_document().unwrap();
1225 assert!(!dumps(&value, &JsonOptions::default()).exact);
1226 let value = Parser::new("[1]").parse_document().unwrap();
1227 let options = JsonOptions {
1228 indent: Some("1".to_string()),
1229 ..JsonOptions::default()
1230 };
1231 assert!(!dumps(&value, &options).exact);
1232 }
1233
1234 fn render(text: &str) -> String {
1235 let console = Console::builder()
1236 .force_terminal(true)
1237 .color_system(Some(ColorSystem::Truecolor))
1238 .width(40)
1239 .build();
1240 console.render_to_string(&Json::new(text).unwrap())
1241 }
1242
1243 fn render_plain(text: &str, width: usize) -> String {
1244 let console = Console::builder().width(width).color_system(None).build();
1245 console.render_to_string(&Json::new(text).expect("valid json"))
1246 }
1247
1248 #[test]
1249 fn empty_collections_stay_inline() {
1250 assert_eq!(render("{}"), "\x1b[1m{\x1b[0m\x1b[1m}\x1b[0m");
1251 assert_eq!(render("[]"), "\x1b[1m[\x1b[0m\x1b[1m]\x1b[0m");
1252 }
1253
1254 #[test]
1255 fn object_with_scalars() {
1256 assert_eq!(
1257 render(r#"{"ok": false}"#),
1258 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"ok\"\x1b[0m: \x1b[3;91mfalse\x1b[0m\n\x1b[1m}\x1b[0m"
1259 );
1260 }
1261
1262 #[test]
1263 fn invalid_json_errors() {
1264 assert!(Json::new("{not json}").is_err());
1265 }
1266
1267 #[test]
1268 fn non_ascii_stays_utf8_in_input_order() {
1269 let out = render("{\"name\": \"caf\u{e9}\", \"emoji\": \"\u{2764}\"}");
1273 assert!(out.contains("caf\u{e9}"), "café stays UTF-8: {out:?}");
1274 assert!(out.contains('\u{2764}'), "heart stays UTF-8");
1275 let name_at = out.find("name").expect("name key present");
1276 let emoji_at = out.find("emoji").expect("emoji key present");
1277 assert!(name_at < emoji_at, "keys keep input order");
1278 }
1279
1280 #[test]
1281 fn a_long_value_is_wrapped_rather_than_cropped() {
1282 let payload = format!("{{\"k\": \"{}\"}}", "y".repeat(120));
1285 let out = render_plain(&payload, 40);
1286 assert_eq!(
1287 out.matches('y').count(),
1288 120,
1289 "characters were dropped:
1290{out}"
1291 );
1292 }
1293
1294 #[test]
1303 fn wrapping_breaks_at_word_boundaries() {
1304 let payload = r#"{"k": "the quick brown fox jumps over the lazy dog and keeps running for a very long time indeed"}"#;
1305 assert_eq!(
1306 render_plain(payload, 40),
1307 "{\n \"k\": \"the quick brown fox jumps over \n\
1308 the lazy dog and keeps running for a \n\
1309 very long time indeed\"\n}"
1310 );
1311 }
1312
1313 #[cfg(feature = "json-escape-safe")]
1314 #[test]
1315 fn adjusted_escape_boundaries_preserve_the_remaining_payload() {
1316 let input = format!(r#"{{"v":"aa\u0001{}"}}"#, "b".repeat(40));
1317 for width in 6..=20 {
1318 let output = Console::builder()
1319 .width(width)
1320 .force_terminal(false)
1321 .build()
1322 .render_to_string(&Json::new(&input).unwrap().escape_safe(true));
1323 assert_eq!(output.matches('b').count(), 40, "width {width}: {output:?}");
1324 }
1325 }
1326
1327 #[cfg(feature = "json-escape-safe")]
1328 #[test]
1329 fn wrapping_keeps_json_escapes_atomic_at_narrow_widths() {
1330 let payload = r#"{"v":"a\"b\\c\nd\u0001e"}"#;
1331 for width in 8..=14 {
1332 let output = Console::builder()
1333 .width(width)
1334 .force_terminal(false)
1335 .build()
1336 .render_to_string(&Json::new(payload).unwrap().escape_safe(true));
1337 for line in output.lines() {
1338 let bytes = line.as_bytes();
1339 let mut index = 0;
1340 while index < bytes.len() {
1341 if bytes[index] != b'\\' {
1342 index += 1;
1343 continue;
1344 }
1345 assert!(
1346 index + 1 < bytes.len(),
1347 "split escape at width {width}: {output:?}"
1348 );
1349 if bytes[index + 1] == b'u' {
1350 assert!(
1351 index + 6 <= bytes.len(),
1352 "split unicode escape at width {width}: {output:?}"
1353 );
1354 index += 6;
1355 } else {
1356 index += 2;
1357 }
1358 }
1359 }
1360 }
1361 }
1362
1363 #[cfg(feature = "json-escape-safe")]
1364 #[test]
1365 fn escape_folding_preserves_bytes_even_below_the_escape_width() {
1366 let payload = r#"{"v":"a\"b\\c\nd\u0001eeeeeeeeeeee"}"#;
1367 let wide = Console::builder()
1368 .width(100)
1369 .force_terminal(false)
1370 .build()
1371 .render_to_string(&Json::new(payload).unwrap().escape_safe(true))
1372 .replace('\n', "");
1373 for width in 1..=20 {
1374 let output = Console::builder()
1375 .width(width)
1376 .force_terminal(false)
1377 .build()
1378 .render_to_string(&Json::new(payload).unwrap().escape_safe(true));
1379 assert_eq!(output.replace('\n', ""), wide, "width {width}");
1380 assert!(
1381 output
1382 .lines()
1383 .all(|line| crate::cells::cell_len(line) <= width)
1384 );
1385 }
1386 }
1387
1388 #[cfg(feature = "json-escape-safe")]
1389 #[test]
1390 fn escape_cropping_never_emits_a_partial_escape() {
1391 let payload = r#""a\"b\\c\nd\u0001eeee""#;
1392 for width in 1..=24 {
1393 let output = Console::builder()
1394 .width(width)
1395 .force_terminal(false)
1396 .build()
1397 .render_to_string(&Json::new(payload).unwrap().no_wrap(true).escape_safe(true));
1398 let mut chars = output.chars();
1399 while let Some(c) = chars.next() {
1400 if c == '\\' {
1401 let next = chars.next().expect("complete short escape");
1402 if next == 'u' {
1403 for _ in 0..4 {
1404 assert!(chars.next().is_some_and(|c| c.is_ascii_hexdigit()));
1405 }
1406 }
1407 }
1408 }
1409 }
1410 }
1411
1412 #[test]
1422 fn a_nested_document_is_cropped_rather_than_wrapped() {
1423 let payload = r#"{"k": "the quick brown fox jumps over the lazy dog and keeps running for a very long time indeed"}"#;
1424 let console = Console::builder().width(40).color_system(None).build();
1425 let json = Json::new(payload).expect("valid json").no_wrap(true);
1426 assert_eq!(
1427 console.render_to_string(&json),
1428 "{\n \"k\": \"the quick brown fox jumps over t\n}"
1429 );
1430
1431 assert_eq!(
1434 render_plain(payload, 40),
1435 "{\n \"k\": \"the quick brown fox jumps over \n\
1436 the lazy dog and keeps running for a \n\
1437 very long time indeed\"\n}"
1438 );
1439 }
1440
1441 #[test]
1445 fn cropping_never_splits_a_wide_character() {
1446 let console = Console::builder().width(12).color_system(None).build();
1447 let json = Json::new("{\"k\": \"\u{1f306}\u{1f306}\u{1f306}\"}")
1448 .expect("valid json")
1449 .no_wrap(true);
1450 for line in console.render_to_string(&json).lines() {
1451 assert!(
1452 crate::cells::cell_len(line) <= 12,
1453 "line {line:?} overflows the crop"
1454 );
1455 }
1456 }
1457
1458 #[test]
1462 fn floats_round_trip_exactly() {
1463 for literal in [
1464 "-938371.9565467801",
1465 "0.1",
1466 "1.7976931348623157e308",
1467 "5e-324",
1468 "3.141592653589793",
1469 ] {
1470 let out = render_plain(&format!("{{\"v\": {literal}}}"), 120);
1471 let rendered: String = out
1472 .split(':')
1473 .nth(1)
1474 .expect("a value after the key")
1475 .trim()
1476 .trim_end_matches(['}', ' ', '\n'])
1477 .to_string();
1478 let want: f64 = literal.parse().expect("literal parses");
1479 let got: f64 = rendered
1480 .parse()
1481 .unwrap_or_else(|_| panic!("rendered {rendered:?}"));
1482 assert_eq!(
1483 got.to_bits(),
1484 want.to_bits(),
1485 "{literal} rendered as {rendered} — a different double"
1486 );
1487 }
1488 }
1489
1490 #[test]
1494 fn deep_nesting_is_not_rejected() {
1495 for depth in [128, 129, 200, 1000] {
1496 let payload = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
1497 let json = Json::new(&payload)
1498 .unwrap_or_else(|error| panic!("depth {depth} rejected: {error}"));
1499 let console = Console::builder()
1500 .width(4 * depth + 8)
1501 .color_system(None)
1502 .build();
1503 let out = console.render_to_string(&json);
1504 assert_eq!(
1505 out.matches('[').count(),
1506 depth,
1507 "depth {depth} did not render every level"
1508 );
1509 }
1510 }
1511
1512 #[test]
1516 fn very_deep_nesting_does_not_overflow_the_stack() {
1517 let payload = format!("{}1{}", "[".repeat(MAX_DEPTH), "]".repeat(MAX_DEPTH));
1518 let json = Json::new(&payload).expect("deep document parses");
1519 drop(json);
1520 let mut node = Node::Number("1".to_string());
1522 for _ in 0..100_000 {
1523 node = Node::Array(vec![node]);
1524 }
1525 drop(node);
1526 }
1527
1528 #[test]
1533 fn nesting_past_the_limit_is_an_error() {
1534 for (open, close, what) in [("[", "]", "array"), ("{\"a\":", "}", "object")] {
1535 for depth in [MAX_DEPTH + 1, 100_000] {
1536 let payload = format!("{}1{}", open.repeat(depth), close.repeat(depth));
1537 let error = Json::new(&payload).err().expect("too deep");
1538 assert!(
1539 error.to_string().contains(&format!(
1540 "maximum recursion depth exceeded while decoding a JSON {what}"
1541 )),
1542 "{error}"
1543 );
1544 }
1545 }
1546 let payload = format!("{}[]{}", "[".repeat(MAX_DEPTH), "]".repeat(MAX_DEPTH));
1548 assert!(Json::new(&payload).is_err());
1549 let payload = format!(
1550 "{}[]{}",
1551 "[".repeat(MAX_DEPTH - 1),
1552 "]".repeat(MAX_DEPTH - 1)
1553 );
1554 assert!(Json::new(&payload).is_ok());
1555 }
1556
1557 #[test]
1565 fn non_finite_numbers_render_unstyled() {
1566 assert_eq!(
1567 render(r#"{"a": NaN, "b": Infinity, "c": -Infinity, "d": 1.5}"#),
1568 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"a\"\x1b[0m: NaN,\n \x1b[1;34m\"b\"\x1b[0m: \
1569 Infinity,\n \x1b[1;34m\"c\"\x1b[0m: -Infinity,\n \x1b[1;34m\"d\"\x1b[0m: \
1570 \x1b[1;36m1.5\x1b[0m\n\x1b[1m}\x1b[0m"
1571 );
1572 for rejected in [r#"{"a": nan}"#, r#"{"a": inf}"#, r#"{"a": -inf}"#] {
1574 assert!(Json::new(rejected).is_err(), "{rejected} should not parse");
1575 }
1576 }
1577
1578 #[test]
1581 fn acceptance_matches_serde_json() {
1582 let samples = [
1583 "{}",
1584 "[]",
1585 " {\t\"a\" :\n1 } ",
1586 r#"{"a": 1, "a": 2}"#,
1587 r#"{"a": [1, {"b": null}], "c": "x"}"#,
1588 "0",
1589 "-0",
1590 "0.0",
1591 "1e10",
1592 "1E+10",
1593 "1e-7",
1594 "12345678901234567890",
1595 "-12345678901234567890123456789012345",
1596 "01",
1597 "1.",
1598 ".1",
1599 "+1",
1600 "1e",
1601 "-",
1602 "--1",
1603 "-i",
1604 "-Inf",
1605 "Infinit",
1606 "NAN",
1607 "1 2",
1608 "",
1609 " ",
1610 "{",
1611 "[",
1612 "]",
1613 "}",
1614 "[,]",
1615 "[1,]",
1616 r#"{"a": 1,}"#,
1617 r#"{a: 1}"#,
1618 r#"{'a': 1}"#,
1619 r#"{"a" 1}"#,
1620 "[1 2]",
1621 "truex",
1622 "tru",
1623 "nul",
1624 r#""unterminated"#,
1625 r#""\q""#,
1626 r#""é""#,
1627 r#""😀""#,
1628 "\"raw\nnewline\"",
1629 r#""café ❤""#,
1630 "\"\u{e9}\\\"",
1631 "\u{feff}{}",
1632 "[[[[1]]]]",
1633 ];
1634 for sample in samples {
1635 let ours = Json::new(sample).is_ok();
1636 let theirs = serde_json::from_str::<serde_json::Value>(sample).is_ok();
1637 assert_eq!(ours, theirs, "disagreed about {sample:?}");
1638 }
1639 }
1640
1641 #[test]
1642 fn python_numbers_preserve_large_integers_and_overflow() {
1643 for number in [
1644 "1234567890123456789012345678901234567890",
1645 "-1234567890123456789012345678901234567890",
1646 ] {
1647 assert_eq!(render_plain(number, 100), number);
1648 }
1649 assert_eq!(render_plain("-0", 100), "0");
1650 assert_eq!(render_plain("1e400", 100), "Infinity");
1651 assert_eq!(render_plain("-1e999", 100), "-Infinity");
1652 for invalid in ["01", "-01", "1.e2", "1e+", "1e400x", "--1", "1+2", ".1"] {
1653 assert!(Json::new(invalid).is_err(), "accepted {invalid}");
1654 }
1655 }
1656
1657 #[test]
1663 fn the_parsed_tree_matches_serde_json() {
1664 let samples = [
1665 r#"{"name": "Alice", "age": 30, "admin": true, "tags": ["a", "b"], "meta": null}"#,
1666 r#"{"a": 1, "b": 2, "a": 3}"#,
1667 r#"{"a": {"b": {"c": [1, [], {}, [[2]]]}}}"#,
1668 r#"{"k": "A\t\"x\"A\\\/é"}"#,
1669 r#"[0, 12345678901234567890, -7]"#,
1670 r#"{"café": "❤", "": ""}"#,
1671 "[]",
1672 "{}",
1673 "\"top level\"",
1674 "1234",
1675 ];
1676 for sample in samples {
1677 let reference: serde_json::Value =
1678 serde_json::from_str(sample).expect("sample is valid JSON");
1679 assert_eq!(
1680 render_plain(sample, 10_000),
1681 serde_json::to_string_pretty(&reference).expect("value re-serialises"),
1682 "diverged on {sample}"
1683 );
1684 }
1685 }
1686
1687 #[test]
1690 fn floats_render_as_python_repr() {
1691 let out = render_plain(
1692 "[-0.5, 1e10, 1E+10, 1e-7, 1.7976931348623157e308, 1e16]",
1693 10_000,
1694 );
1695 let values: Vec<&str> = out
1696 .lines()
1697 .filter_map(|line| line.trim().strip_suffix(',').or(Some(line.trim())))
1698 .filter(|value| !matches!(*value, "[" | "]"))
1699 .collect();
1700 assert_eq!(
1701 values,
1702 [
1703 "-0.5",
1704 "10000000000.0",
1705 "10000000000.0",
1706 "1e-07",
1707 "1.7976931348623157e+308",
1708 "1e+16"
1709 ]
1710 );
1711 }
1712
1713 #[test]
1716 fn a_repeated_key_keeps_its_position_and_last_value() {
1717 assert_eq!(
1718 render_plain(r#"{"a": 1, "b": 2, "a": 3}"#, 40),
1719 "{\n \"a\": 3,\n \"b\": 2\n}"
1720 );
1721 }
1722
1723 #[test]
1726 fn escapes_are_re_encoded_like_dumps() {
1727 assert_eq!(
1728 render_plain(r#"{"k": "A\t\"x\""}"#, 60),
1729 "{\n \"k\": \"A\\t\\\"x\\\"\"\n}"
1730 );
1731 }
1732}