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 if let Some(literal) = first_non_finite(&value) {
200 return Err(RichError::Json(format!(
201 "Out of range float values are not JSON compliant: {}",
202 match literal {
203 "NaN" => "nan",
204 "Infinity" => "inf",
205 _ => "-inf",
206 }
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('\\') && lookahead.next() == Some('u') {
794 if let Some(low @ 0xDC00..=0xDFFF) = hex4(&mut lookahead) {
795 unit = 0x10000 + ((unit - 0xD800) << 10) + (low - 0xDC00);
796 chars = lookahead;
797 }
798 }
799 }
800 match char::from_u32(unit) {
801 Some(c) => c,
802 None => {
803 out.push(0xE0 | (unit >> 12) as u8);
805 out.push(0x80 | ((unit >> 6) & 0x3F) as u8);
806 out.push(0x80 | (unit & 0x3F) as u8);
807 continue;
808 }
809 }
810 }
811 _ => return None,
812 };
813 let mut buf = [0u8; 4];
814 out.extend_from_slice(decoded.encode_utf8(&mut buf).as_bytes());
815 }
816 Some(out)
817}
818
819fn quote(string: &str) -> String {
820 serde_json::to_string(string).unwrap_or_else(|_| format!("{string:?}"))
821}
822
823enum Frame {
825 Array(Vec<Node>),
826 Object {
827 entries: Vec<(PyStr, Node)>,
828 seen: HashMap<PyStr, usize>,
832 key: PyStr,
834 },
835}
836
837struct Parser<'a> {
841 src: &'a str,
842 bytes: &'a [u8],
843 pos: usize,
844}
845
846impl<'a> Parser<'a> {
847 fn new(src: &'a str) -> Self {
848 Parser {
849 src,
850 bytes: src.as_bytes(),
851 pos: 0,
852 }
853 }
854
855 fn parse_document(&mut self) -> Result<Node> {
856 let value = self.parse_value()?;
857 self.skip_whitespace();
858 if self.pos != self.bytes.len() {
859 return Err(self.error("trailing characters"));
860 }
861 Ok(value)
862 }
863
864 fn parse_value(&mut self) -> Result<Node> {
866 let mut stack: Vec<Frame> = Vec::new();
867 let mut node: Node;
868
869 'descend: loop {
870 self.skip_whitespace();
871 if let Some(kind @ (b'[' | b'{')) = self.peek() {
874 if stack.len() >= MAX_DEPTH {
875 let what = if kind == b'[' { "array" } else { "object" };
876 return Err(self.error(&format!(
877 "maximum recursion depth exceeded while decoding a JSON {what} \
878 from a unicode string"
879 )));
880 }
881 }
882 match self.peek() {
883 Some(b'[') => {
884 self.pos += 1;
885 self.skip_whitespace();
886 if self.peek() == Some(b']') {
887 self.pos += 1;
888 node = Node::Array(Vec::new());
889 } else {
890 stack.push(Frame::Array(Vec::new()));
891 continue 'descend;
892 }
893 }
894 Some(b'{') => {
895 self.pos += 1;
896 self.skip_whitespace();
897 if self.peek() == Some(b'}') {
898 self.pos += 1;
899 node = Node::Object(Vec::new());
900 } else {
901 let key = self.parse_key()?;
902 stack.push(Frame::Object {
903 entries: Vec::new(),
904 seen: HashMap::new(),
905 key,
906 });
907 continue 'descend;
908 }
909 }
910 _ => node = self.parse_scalar()?,
911 }
912
913 loop {
916 let Some(frame) = stack.last_mut() else {
917 return Ok(node);
918 };
919 let closing = match frame {
920 Frame::Array(items) => {
921 items.push(node);
922 b']'
923 }
924 Frame::Object { entries, seen, key } => {
925 let key = std::mem::take(key);
926 match seen.get(&key) {
927 Some(&at) => entries[at].1 = node,
928 None => {
929 seen.insert(key.clone(), entries.len());
930 entries.push((key, node));
931 }
932 }
933 b'}'
934 }
935 };
936 self.skip_whitespace();
937 match self.peek() {
938 Some(b',') => {
939 self.pos += 1;
940 if closing == b'}' {
941 let next_key = self.parse_key()?;
942 if let Some(Frame::Object { key, .. }) = stack.last_mut() {
943 *key = next_key;
944 }
945 }
946 continue 'descend;
947 }
948 Some(byte) if byte == closing => {
949 self.pos += 1;
950 node = match stack.pop() {
951 Some(Frame::Array(items)) => Node::Array(items),
952 Some(Frame::Object { entries, .. }) => Node::Object(entries),
953 None => unreachable!("the frame was just borrowed"),
954 };
955 }
956 _ if closing == b']' => return Err(self.error("expected `,` or `]`")),
957 _ => return Err(self.error("expected `,` or `}`")),
958 }
959 }
960 }
961 }
962
963 fn parse_key(&mut self) -> Result<PyStr> {
965 self.skip_whitespace();
966 if self.peek() != Some(b'"') {
967 return Err(self.error("key must be a string"));
968 }
969 let key = self.parse_string()?;
970 self.skip_whitespace();
971 if self.peek() != Some(b':') {
972 return Err(self.error("expected `:`"));
973 }
974 self.pos += 1;
975 Ok(key)
976 }
977
978 fn parse_scalar(&mut self) -> Result<Node> {
979 match self.peek() {
980 Some(b'"') => Ok(Node::Str(self.parse_string()?)),
981 Some(b't') => {
982 self.expect_literal("true")?;
983 Ok(Node::Bool(true))
984 }
985 Some(b'f') => {
986 self.expect_literal("false")?;
987 Ok(Node::Bool(false))
988 }
989 Some(b'n') => {
990 self.expect_literal("null")?;
991 Ok(Node::Null)
992 }
993 Some(b'N') => {
997 self.expect_literal("NaN")?;
998 Ok(Node::NonFinite("NaN"))
999 }
1000 Some(b'I') => {
1001 self.expect_literal("Infinity")?;
1002 Ok(Node::NonFinite("Infinity"))
1003 }
1004 Some(b'-') if self.src[self.pos..].starts_with("-Infinity") => {
1005 self.pos += "-Infinity".len();
1006 Ok(Node::NonFinite("-Infinity"))
1007 }
1008 Some(b'-' | b'0'..=b'9') => self.parse_number(),
1009 Some(_) => Err(self.error("expected value")),
1010 None => Err(self.error("EOF while parsing a value")),
1011 }
1012 }
1013
1014 fn parse_string(&mut self) -> Result<PyStr> {
1017 let start = self.pos;
1018 let mut end = self.pos + 1;
1019 loop {
1020 match self.bytes.get(end) {
1021 None => return Err(self.error_at(self.bytes.len(), "EOF while parsing a string")),
1022 Some(b'"') => {
1023 end += 1;
1024 break;
1025 }
1026 Some(b'\\') => {
1027 end += 1;
1028 match self.src[end..].chars().next() {
1033 Some(ch) => end += ch.len_utf8(),
1034 None => {
1035 return Err(
1036 self.error_at(self.bytes.len(), "EOF while parsing a string")
1037 )
1038 }
1039 }
1040 }
1041 Some(_) => end += 1,
1044 }
1045 }
1046 let token = &self.src[start..end];
1047 let decoded = match serde_json::from_str::<String>(token) {
1048 Ok(decoded) => decoded.into_bytes(),
1049 Err(error) => decode_with_surrogates(token)
1052 .ok_or_else(|| self.error_at(start, &describe(&error)))?,
1053 };
1054 self.pos = end;
1055 Ok(decoded)
1056 }
1057
1058 fn parse_number(&mut self) -> Result<Node> {
1061 let start = self.pos;
1062 let mut end = start;
1063 while matches!(
1064 self.bytes.get(end),
1065 Some(b'-' | b'+' | b'.' | b'e' | b'E' | b'0'..=b'9')
1066 ) {
1067 end += 1;
1068 }
1069 let token = &self.src[start..end];
1070 let digits = token.as_bytes();
1071 let mut i = usize::from(digits.first() == Some(&b'-'));
1072 if digits.get(i) == Some(&b'0') {
1073 i += 1;
1074 } else {
1075 let first = i;
1076 while digits.get(i).is_some_and(u8::is_ascii_digit) {
1077 i += 1;
1078 }
1079 if i == first {
1080 return Err(self.error_at(start, "invalid number"));
1081 }
1082 }
1083 let mut floating = false;
1084 if digits.get(i) == Some(&b'.') {
1085 floating = true;
1086 i += 1;
1087 let first = i;
1088 while digits.get(i).is_some_and(u8::is_ascii_digit) {
1089 i += 1;
1090 }
1091 if i == first {
1092 return Err(self.error_at(start, "invalid number"));
1093 }
1094 }
1095 if matches!(digits.get(i), Some(b'e' | b'E')) {
1096 floating = true;
1097 i += 1;
1098 if matches!(digits.get(i), Some(b'+' | b'-')) {
1099 i += 1;
1100 }
1101 let first = i;
1102 while digits.get(i).is_some_and(u8::is_ascii_digit) {
1103 i += 1;
1104 }
1105 if i == first {
1106 return Err(self.error_at(start, "invalid number"));
1107 }
1108 }
1109 if i != digits.len() {
1110 return Err(self.error_at(start, "invalid number"));
1111 }
1112 self.pos = end;
1113 if !floating {
1114 return Ok(Node::Number(
1115 if token == "-0" { "0" } else { token }.to_string(),
1116 ));
1117 }
1118 let value: f64 = token
1119 .parse()
1120 .map_err(|_| self.error_at(start, "invalid number"))?;
1121 if value.is_infinite() {
1122 return Ok(Node::NonFinite(if value.is_sign_negative() {
1123 "-Infinity"
1124 } else {
1125 "Infinity"
1126 }));
1127 }
1128 Ok(Node::Number(crate::pyformat::float_repr(value)))
1130 }
1131
1132 fn expect_literal(&mut self, literal: &str) -> Result<()> {
1133 if self.src[self.pos..].starts_with(literal) {
1134 self.pos += literal.len();
1135 Ok(())
1136 } else {
1137 Err(self.error("expected value"))
1138 }
1139 }
1140
1141 fn peek(&self) -> Option<u8> {
1142 self.bytes.get(self.pos).copied()
1143 }
1144
1145 fn skip_whitespace(&mut self) {
1146 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
1147 self.pos += 1;
1148 }
1149 }
1150
1151 fn error(&self, message: &str) -> RichError {
1152 self.error_at(self.pos, message)
1153 }
1154
1155 fn error_at(&self, pos: usize, message: &str) -> RichError {
1156 let (line, column) = self.line_column(pos);
1157 RichError::Json(format!("{message} at line {line} column {column}"))
1158 }
1159
1160 fn line_column(&self, pos: usize) -> (usize, usize) {
1161 let mut pos = pos.min(self.src.len());
1162 while !self.src.is_char_boundary(pos) {
1163 pos -= 1;
1164 }
1165 let before = &self.src[..pos];
1166 let line = 1 + before.matches('\n').count();
1167 let column = before
1168 .rsplit('\n')
1169 .next()
1170 .map_or(0, |tail| tail.chars().count())
1171 + 1;
1172 (line, column)
1173 }
1174}
1175
1176fn describe(error: &serde_json::Error) -> String {
1179 let text = error.to_string();
1180 match text.find(" at line ") {
1181 Some(at) => text[..at].to_string(),
1182 None => text,
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189 use crate::color::ColorSystem;
1190
1191 #[test]
1194 fn recorded_spans_match_the_highlighter_regexes() {
1195 let documents = [
1196 r#"{"name": "Alice", "age": 30, "admin": true, "tags": ["a", "b"], "meta": null}"#,
1197 r#"[1e20, -0.0, 1.5e-7, 12345678901234567890, false, [], {}, [[]], {"a": {}}]"#,
1198 r#"{"true": "false", "x:y": "a\"b", "q\"": ": null", "n": [NaN, Infinity, -Infinity]}"#,
1199 r#"{"caf\u00e9": "\u2764 \ud83d\ude00", "ctl": "\u0001\t\n", "": ""}"#,
1200 r#"["[{(1)}]", "0x1F", "b\"x", "\\\"", "tail\\x"]"#,
1201 ];
1202 let indents = [None, Some(" "), Some("\t"), Some("")];
1203 for document in documents {
1204 for indent in indents {
1205 for ensure_ascii in [false, true] {
1206 let options = JsonOptions {
1207 indent: indent.map(str::to_string),
1208 ensure_ascii,
1209 sort_keys: true,
1210 ..JsonOptions::default()
1211 };
1212 let value = Parser::new(document).parse_document().unwrap();
1213 let dumped = dumps(&value, &options);
1214 assert!(dumped.exact, "{document} / {indent:?}");
1215 let mut recorded = dumped.spans.clone();
1216 recorded.extend(dumped.keys.clone());
1217 let expected = json_highlight(dumped.json.clone());
1218 assert_eq!(recorded, expected.spans(), "{document} / {indent:?}");
1219 }
1220 }
1221 }
1222 let value = Parser::new(r#"{"k": "a\\"}"#).parse_document().unwrap();
1224 assert!(!dumps(&value, &JsonOptions::default()).exact);
1225 let value = Parser::new("[1]").parse_document().unwrap();
1226 let options = JsonOptions {
1227 indent: Some("1".to_string()),
1228 ..JsonOptions::default()
1229 };
1230 assert!(!dumps(&value, &options).exact);
1231 }
1232
1233 fn render(text: &str) -> String {
1234 let console = Console::builder()
1235 .force_terminal(true)
1236 .color_system(Some(ColorSystem::Truecolor))
1237 .width(40)
1238 .build();
1239 console.render_to_string(&Json::new(text).unwrap())
1240 }
1241
1242 fn render_plain(text: &str, width: usize) -> String {
1243 let console = Console::builder().width(width).color_system(None).build();
1244 console.render_to_string(&Json::new(text).expect("valid json"))
1245 }
1246
1247 #[test]
1248 fn empty_collections_stay_inline() {
1249 assert_eq!(render("{}"), "\x1b[1m{\x1b[0m\x1b[1m}\x1b[0m");
1250 assert_eq!(render("[]"), "\x1b[1m[\x1b[0m\x1b[1m]\x1b[0m");
1251 }
1252
1253 #[test]
1254 fn object_with_scalars() {
1255 assert_eq!(
1256 render(r#"{"ok": false}"#),
1257 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"ok\"\x1b[0m: \x1b[3;91mfalse\x1b[0m\n\x1b[1m}\x1b[0m"
1258 );
1259 }
1260
1261 #[test]
1262 fn invalid_json_errors() {
1263 assert!(Json::new("{not json}").is_err());
1264 }
1265
1266 #[test]
1267 fn non_ascii_stays_utf8_in_input_order() {
1268 let out = render("{\"name\": \"caf\u{e9}\", \"emoji\": \"\u{2764}\"}");
1272 assert!(out.contains("caf\u{e9}"), "café stays UTF-8: {out:?}");
1273 assert!(out.contains('\u{2764}'), "heart stays UTF-8");
1274 let name_at = out.find("name").expect("name key present");
1275 let emoji_at = out.find("emoji").expect("emoji key present");
1276 assert!(name_at < emoji_at, "keys keep input order");
1277 }
1278
1279 #[test]
1280 fn a_long_value_is_wrapped_rather_than_cropped() {
1281 let payload = format!("{{\"k\": \"{}\"}}", "y".repeat(120));
1284 let out = render_plain(&payload, 40);
1285 assert_eq!(
1286 out.matches('y').count(),
1287 120,
1288 "characters were dropped:
1289{out}"
1290 );
1291 }
1292
1293 #[test]
1302 fn wrapping_breaks_at_word_boundaries() {
1303 let payload = r#"{"k": "the quick brown fox jumps over the lazy dog and keeps running for a very long time indeed"}"#;
1304 assert_eq!(
1305 render_plain(payload, 40),
1306 "{\n \"k\": \"the quick brown fox jumps over \n\
1307 the lazy dog and keeps running for a \n\
1308 very long time indeed\"\n}"
1309 );
1310 }
1311
1312 #[cfg(feature = "json-escape-safe")]
1313 #[test]
1314 fn adjusted_escape_boundaries_preserve_the_remaining_payload() {
1315 let input = format!(r#"{{"v":"aa\u0001{}"}}"#, "b".repeat(40));
1316 for width in 6..=20 {
1317 let output = Console::builder()
1318 .width(width)
1319 .force_terminal(false)
1320 .build()
1321 .render_to_string(&Json::new(&input).unwrap().escape_safe(true));
1322 assert_eq!(output.matches('b').count(), 40, "width {width}: {output:?}");
1323 }
1324 }
1325
1326 #[cfg(feature = "json-escape-safe")]
1327 #[test]
1328 fn wrapping_keeps_json_escapes_atomic_at_narrow_widths() {
1329 let payload = r#"{"v":"a\"b\\c\nd\u0001e"}"#;
1330 for width in 8..=14 {
1331 let output = Console::builder()
1332 .width(width)
1333 .force_terminal(false)
1334 .build()
1335 .render_to_string(&Json::new(payload).unwrap().escape_safe(true));
1336 for line in output.lines() {
1337 let bytes = line.as_bytes();
1338 let mut index = 0;
1339 while index < bytes.len() {
1340 if bytes[index] != b'\\' {
1341 index += 1;
1342 continue;
1343 }
1344 assert!(
1345 index + 1 < bytes.len(),
1346 "split escape at width {width}: {output:?}"
1347 );
1348 if bytes[index + 1] == b'u' {
1349 assert!(
1350 index + 6 <= bytes.len(),
1351 "split unicode escape at width {width}: {output:?}"
1352 );
1353 index += 6;
1354 } else {
1355 index += 2;
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 #[cfg(feature = "json-escape-safe")]
1363 #[test]
1364 fn escape_folding_preserves_bytes_even_below_the_escape_width() {
1365 let payload = r#"{"v":"a\"b\\c\nd\u0001eeeeeeeeeeee"}"#;
1366 let wide = Console::builder()
1367 .width(100)
1368 .force_terminal(false)
1369 .build()
1370 .render_to_string(&Json::new(payload).unwrap().escape_safe(true))
1371 .replace('\n', "");
1372 for width in 1..=20 {
1373 let output = Console::builder()
1374 .width(width)
1375 .force_terminal(false)
1376 .build()
1377 .render_to_string(&Json::new(payload).unwrap().escape_safe(true));
1378 assert_eq!(output.replace('\n', ""), wide, "width {width}");
1379 assert!(output
1380 .lines()
1381 .all(|line| crate::cells::cell_len(line) <= width));
1382 }
1383 }
1384
1385 #[cfg(feature = "json-escape-safe")]
1386 #[test]
1387 fn escape_cropping_never_emits_a_partial_escape() {
1388 let payload = r#""a\"b\\c\nd\u0001eeee""#;
1389 for width in 1..=24 {
1390 let output = Console::builder()
1391 .width(width)
1392 .force_terminal(false)
1393 .build()
1394 .render_to_string(&Json::new(payload).unwrap().no_wrap(true).escape_safe(true));
1395 let mut chars = output.chars();
1396 while let Some(c) = chars.next() {
1397 if c == '\\' {
1398 let next = chars.next().expect("complete short escape");
1399 if next == 'u' {
1400 for _ in 0..4 {
1401 assert!(chars.next().is_some_and(|c| c.is_ascii_hexdigit()));
1402 }
1403 }
1404 }
1405 }
1406 }
1407 }
1408
1409 #[test]
1419 fn a_nested_document_is_cropped_rather_than_wrapped() {
1420 let payload = r#"{"k": "the quick brown fox jumps over the lazy dog and keeps running for a very long time indeed"}"#;
1421 let console = Console::builder().width(40).color_system(None).build();
1422 let json = Json::new(payload).expect("valid json").no_wrap(true);
1423 assert_eq!(
1424 console.render_to_string(&json),
1425 "{\n \"k\": \"the quick brown fox jumps over t\n}"
1426 );
1427
1428 assert_eq!(
1431 render_plain(payload, 40),
1432 "{\n \"k\": \"the quick brown fox jumps over \n\
1433 the lazy dog and keeps running for a \n\
1434 very long time indeed\"\n}"
1435 );
1436 }
1437
1438 #[test]
1442 fn cropping_never_splits_a_wide_character() {
1443 let console = Console::builder().width(12).color_system(None).build();
1444 let json = Json::new("{\"k\": \"\u{1f306}\u{1f306}\u{1f306}\"}")
1445 .expect("valid json")
1446 .no_wrap(true);
1447 for line in console.render_to_string(&json).lines() {
1448 assert!(
1449 crate::cells::cell_len(line) <= 12,
1450 "line {line:?} overflows the crop"
1451 );
1452 }
1453 }
1454
1455 #[test]
1459 fn floats_round_trip_exactly() {
1460 for literal in [
1461 "-938371.9565467801",
1462 "0.1",
1463 "1.7976931348623157e308",
1464 "5e-324",
1465 "3.141592653589793",
1466 ] {
1467 let out = render_plain(&format!("{{\"v\": {literal}}}"), 120);
1468 let rendered: String = out
1469 .split(':')
1470 .nth(1)
1471 .expect("a value after the key")
1472 .trim()
1473 .trim_end_matches(['}', ' ', '\n'])
1474 .to_string();
1475 let want: f64 = literal.parse().expect("literal parses");
1476 let got: f64 = rendered
1477 .parse()
1478 .unwrap_or_else(|_| panic!("rendered {rendered:?}"));
1479 assert_eq!(
1480 got.to_bits(),
1481 want.to_bits(),
1482 "{literal} rendered as {rendered} — a different double"
1483 );
1484 }
1485 }
1486
1487 #[test]
1491 fn deep_nesting_is_not_rejected() {
1492 for depth in [128, 129, 200, 1000] {
1493 let payload = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
1494 let json = Json::new(&payload)
1495 .unwrap_or_else(|error| panic!("depth {depth} rejected: {error}"));
1496 let console = Console::builder()
1497 .width(4 * depth + 8)
1498 .color_system(None)
1499 .build();
1500 let out = console.render_to_string(&json);
1501 assert_eq!(
1502 out.matches('[').count(),
1503 depth,
1504 "depth {depth} did not render every level"
1505 );
1506 }
1507 }
1508
1509 #[test]
1513 fn very_deep_nesting_does_not_overflow_the_stack() {
1514 let payload = format!("{}1{}", "[".repeat(MAX_DEPTH), "]".repeat(MAX_DEPTH));
1515 let json = Json::new(&payload).expect("deep document parses");
1516 drop(json);
1517 let mut node = Node::Number("1".to_string());
1519 for _ in 0..100_000 {
1520 node = Node::Array(vec![node]);
1521 }
1522 drop(node);
1523 }
1524
1525 #[test]
1530 fn nesting_past_the_limit_is_an_error() {
1531 for (open, close, what) in [("[", "]", "array"), ("{\"a\":", "}", "object")] {
1532 for depth in [MAX_DEPTH + 1, 100_000] {
1533 let payload = format!("{}1{}", open.repeat(depth), close.repeat(depth));
1534 let error = Json::new(&payload).err().expect("too deep");
1535 assert!(
1536 error.to_string().contains(&format!(
1537 "maximum recursion depth exceeded while decoding a JSON {what}"
1538 )),
1539 "{error}"
1540 );
1541 }
1542 }
1543 let payload = format!("{}[]{}", "[".repeat(MAX_DEPTH), "]".repeat(MAX_DEPTH));
1545 assert!(Json::new(&payload).is_err());
1546 let payload = format!(
1547 "{}[]{}",
1548 "[".repeat(MAX_DEPTH - 1),
1549 "]".repeat(MAX_DEPTH - 1)
1550 );
1551 assert!(Json::new(&payload).is_ok());
1552 }
1553
1554 #[test]
1562 fn non_finite_numbers_render_unstyled() {
1563 assert_eq!(
1564 render(r#"{"a": NaN, "b": Infinity, "c": -Infinity, "d": 1.5}"#),
1565 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"a\"\x1b[0m: NaN,\n \x1b[1;34m\"b\"\x1b[0m: \
1566 Infinity,\n \x1b[1;34m\"c\"\x1b[0m: -Infinity,\n \x1b[1;34m\"d\"\x1b[0m: \
1567 \x1b[1;36m1.5\x1b[0m\n\x1b[1m}\x1b[0m"
1568 );
1569 for rejected in [r#"{"a": nan}"#, r#"{"a": inf}"#, r#"{"a": -inf}"#] {
1571 assert!(Json::new(rejected).is_err(), "{rejected} should not parse");
1572 }
1573 }
1574
1575 #[test]
1578 fn acceptance_matches_serde_json() {
1579 let samples = [
1580 "{}",
1581 "[]",
1582 " {\t\"a\" :\n1 } ",
1583 r#"{"a": 1, "a": 2}"#,
1584 r#"{"a": [1, {"b": null}], "c": "x"}"#,
1585 "0",
1586 "-0",
1587 "0.0",
1588 "1e10",
1589 "1E+10",
1590 "1e-7",
1591 "12345678901234567890",
1592 "-12345678901234567890123456789012345",
1593 "01",
1594 "1.",
1595 ".1",
1596 "+1",
1597 "1e",
1598 "-",
1599 "--1",
1600 "-i",
1601 "-Inf",
1602 "Infinit",
1603 "NAN",
1604 "1 2",
1605 "",
1606 " ",
1607 "{",
1608 "[",
1609 "]",
1610 "}",
1611 "[,]",
1612 "[1,]",
1613 r#"{"a": 1,}"#,
1614 r#"{a: 1}"#,
1615 r#"{'a': 1}"#,
1616 r#"{"a" 1}"#,
1617 "[1 2]",
1618 "truex",
1619 "tru",
1620 "nul",
1621 r#""unterminated"#,
1622 r#""\q""#,
1623 r#""é""#,
1624 r#""😀""#,
1625 "\"raw\nnewline\"",
1626 r#""café ❤""#,
1627 "\"\u{e9}\\\"",
1628 "\u{feff}{}",
1629 "[[[[1]]]]",
1630 ];
1631 for sample in samples {
1632 let ours = Json::new(sample).is_ok();
1633 let theirs = serde_json::from_str::<serde_json::Value>(sample).is_ok();
1634 assert_eq!(ours, theirs, "disagreed about {sample:?}");
1635 }
1636 }
1637
1638 #[test]
1639 fn python_numbers_preserve_large_integers_and_overflow() {
1640 for number in [
1641 "1234567890123456789012345678901234567890",
1642 "-1234567890123456789012345678901234567890",
1643 ] {
1644 assert_eq!(render_plain(number, 100), number);
1645 }
1646 assert_eq!(render_plain("-0", 100), "0");
1647 assert_eq!(render_plain("1e400", 100), "Infinity");
1648 assert_eq!(render_plain("-1e999", 100), "-Infinity");
1649 for invalid in ["01", "-01", "1.e2", "1e+", "1e400x", "--1", "1+2", ".1"] {
1650 assert!(Json::new(invalid).is_err(), "accepted {invalid}");
1651 }
1652 }
1653
1654 #[test]
1660 fn the_parsed_tree_matches_serde_json() {
1661 let samples = [
1662 r#"{"name": "Alice", "age": 30, "admin": true, "tags": ["a", "b"], "meta": null}"#,
1663 r#"{"a": 1, "b": 2, "a": 3}"#,
1664 r#"{"a": {"b": {"c": [1, [], {}, [[2]]]}}}"#,
1665 r#"{"k": "A\t\"x\"A\\\/é"}"#,
1666 r#"[0, 12345678901234567890, -7]"#,
1667 r#"{"café": "❤", "": ""}"#,
1668 "[]",
1669 "{}",
1670 "\"top level\"",
1671 "1234",
1672 ];
1673 for sample in samples {
1674 let reference: serde_json::Value =
1675 serde_json::from_str(sample).expect("sample is valid JSON");
1676 assert_eq!(
1677 render_plain(sample, 10_000),
1678 serde_json::to_string_pretty(&reference).expect("value re-serialises"),
1679 "diverged on {sample}"
1680 );
1681 }
1682 }
1683
1684 #[test]
1687 fn floats_render_as_python_repr() {
1688 let out = render_plain(
1689 "[-0.5, 1e10, 1E+10, 1e-7, 1.7976931348623157e308, 1e16]",
1690 10_000,
1691 );
1692 let values: Vec<&str> = out
1693 .lines()
1694 .filter_map(|line| line.trim().strip_suffix(',').or(Some(line.trim())))
1695 .filter(|value| !matches!(*value, "[" | "]"))
1696 .collect();
1697 assert_eq!(
1698 values,
1699 [
1700 "-0.5",
1701 "10000000000.0",
1702 "10000000000.0",
1703 "1e-07",
1704 "1.7976931348623157e+308",
1705 "1e+16"
1706 ]
1707 );
1708 }
1709
1710 #[test]
1713 fn a_repeated_key_keeps_its_position_and_last_value() {
1714 assert_eq!(
1715 render_plain(r#"{"a": 1, "b": 2, "a": 3}"#, 40),
1716 "{\n \"a\": 3,\n \"b\": 2\n}"
1717 );
1718 }
1719
1720 #[test]
1723 fn escapes_are_re_encoded_like_dumps() {
1724 assert_eq!(
1725 render_plain(r#"{"k": "A\t\"x\""}"#, 60),
1726 "{\n \"k\": \"A\\t\\\"x\\\"\"\n}"
1727 );
1728 }
1729}