Skip to main content

yapi_ai/
json_parse.rs

1//! Lenient JSON parsing for model output: tool-call arguments and server-sent
2//! event payloads.
3
4use serde_json::{Map, Number, Value};
5
6const VALID_ESCAPES: &[char] = &['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u'];
7
8/// Escapes what models commonly emit raw inside JSON strings: control characters,
9/// lone backslashes and invalid escapes. Valid JSON is returned unchanged.
10pub fn repair_json(json: &str) -> String {
11    let chars: Vec<char> = json.chars().collect();
12    let mut repaired = String::with_capacity(json.len());
13    let mut in_string = false;
14    let mut index = 0;
15    while index < chars.len() {
16        let char = chars[index];
17        if !in_string {
18            repaired.push(char);
19            in_string = char == '"';
20        } else if char == '"' {
21            repaired.push(char);
22            in_string = false;
23        } else if char == '\\' {
24            match chars.get(index + 1) {
25                None => repaired.push_str("\\\\"),
26                Some('u')
27                    if chars.len() >= index + 6
28                        && chars[index + 2..index + 6]
29                            .iter()
30                            .all(char::is_ascii_hexdigit) =>
31                {
32                    repaired.extend(&chars[index..index + 6]);
33                    index += 5;
34                }
35                Some(next) if VALID_ESCAPES.contains(next) => {
36                    repaired.push('\\');
37                    repaired.push(*next);
38                    index += 1;
39                }
40                Some(_) => repaired.push_str("\\\\"),
41            }
42        } else if (char as u32) < 0x20 {
43            match char {
44                '\u{8}' => repaired.push_str("\\b"),
45                '\u{c}' => repaired.push_str("\\f"),
46                '\n' => repaired.push_str("\\n"),
47                '\r' => repaired.push_str("\\r"),
48                '\t' => repaired.push_str("\\t"),
49                _ => repaired.push_str(&format!("\\u{:04x}", char as u32)),
50            }
51        } else {
52            repaired.push(char);
53        }
54        index += 1;
55    }
56    repaired
57}
58
59/// Parses JSON, retrying once on the [`repair_json`] form.
60pub fn parse_json_with_repair(json: &str) -> Result<Value, serde_json::Error> {
61    serde_json::from_str(json).or_else(|err| {
62        let repaired = repair_json(json);
63        if repaired == json {
64            Err(err)
65        } else {
66            serde_json::from_str(&repaired)
67        }
68    })
69}
70
71/// The message of the error JavaScript's `JSON.parse` throws for `text`, as
72/// V8 words it, or `None` when `text` is valid JSON.
73pub fn js_parse_error(text: &str) -> Option<String> {
74    let units: Vec<u16> = text.encode_utf16().collect();
75    let mut scan = V8Scan {
76        units: &units,
77        at: 0,
78    };
79    let failure = match scan.value() {
80        Ok(()) => {
81            scan.skip_whitespace();
82            if scan.at == units.len() {
83                return None;
84            }
85            Some("Unexpected non-whitespace character after JSON")
86        }
87        Err(failure) => failure,
88    };
89    Some(scan.message(text, failure))
90}
91
92/// A cursor over UTF-16 units, as V8's JSON parser reads them. A failure is
93/// at the cursor, with V8's message for it, or `None` to name the token there.
94struct V8Scan<'a> {
95    units: &'a [u16],
96    at: usize,
97}
98
99type V8Failure = Option<&'static str>;
100
101impl V8Scan<'_> {
102    fn peek(&self) -> Option<u8> {
103        let unit = *self.units.get(self.at)?;
104        // Units past ASCII are ILLEGAL tokens, as any unexpected character.
105        Some(u8::try_from(unit).unwrap_or(0xFF))
106    }
107
108    fn skip_whitespace(&mut self) {
109        while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
110            self.at += 1;
111        }
112    }
113
114    /// Skips whitespace and then `token`, or fails with `message`.
115    fn expect(&mut self, token: u8, message: &'static str) -> Result<(), V8Failure> {
116        self.skip_whitespace();
117        if self.peek() != Some(token) {
118            return Err(Some(message));
119        }
120        self.at += 1;
121        Ok(())
122    }
123
124    fn digits(&mut self) -> usize {
125        let start = self.at;
126        while self.peek().is_some_and(|unit| unit.is_ascii_digit()) {
127            self.at += 1;
128        }
129        self.at - start
130    }
131
132    /// A value, read iteratively as V8 does, so nesting depth is bounded
133    /// only by the input.
134    fn value(&mut self) -> Result<(), V8Failure> {
135        // The closing token of each container whose next element is pending.
136        let mut open = Vec::new();
137        loop {
138            self.skip_whitespace();
139            match self.peek() {
140                Some(b'"') => self.string()?,
141                Some(b'-' | b'0'..=b'9') => self.number()?,
142                Some(b't') => self.literal(b"true")?,
143                Some(b'f') => self.literal(b"false")?,
144                Some(b'n') => self.literal(b"null")?,
145                Some(token @ (b'{' | b'[')) => {
146                    let close = if token == b'{' { b'}' } else { b']' };
147                    self.at += 1;
148                    self.skip_whitespace();
149                    if self.peek() == Some(close) {
150                        self.at += 1;
151                    } else {
152                        if close == b'}' {
153                            self.property("Expected property name or '}' in JSON")?;
154                        }
155                        open.push(close);
156                        continue;
157                    }
158                }
159                _ => return Err(None),
160            }
161            loop {
162                let Some(&close) = open.last() else {
163                    return Ok(());
164                };
165                self.skip_whitespace();
166                if self.peek() == Some(b',') {
167                    self.at += 1;
168                    if close == b'}' {
169                        self.property("Expected double-quoted property name in JSON")?;
170                    }
171                    break;
172                }
173                self.expect(
174                    close,
175                    if close == b'}' {
176                        "Expected ',' or '}' after property value in JSON"
177                    } else {
178                        "Expected ',' or ']' after array element in JSON"
179                    },
180                )?;
181                open.pop();
182            }
183        }
184    }
185
186    /// A property name and its colon, or a failure with `message` when no
187    /// name starts here.
188    fn property(&mut self, message: &'static str) -> Result<(), V8Failure> {
189        self.skip_whitespace();
190        if self.peek() != Some(b'"') {
191            return Err(Some(message));
192        }
193        self.string()?;
194        self.expect(b':', "Expected ':' after property name in JSON")
195    }
196
197    fn literal(&mut self, word: &[u8]) -> Result<(), V8Failure> {
198        for &unit in word {
199            if self.peek() != Some(unit) {
200                return Err(None);
201            }
202            self.at += 1;
203        }
204        Ok(())
205    }
206
207    fn number(&mut self) -> Result<(), V8Failure> {
208        if self.peek() == Some(b'-') {
209            self.at += 1;
210        }
211        if self.peek() == Some(b'0') {
212            self.at += 1;
213            if self.peek().is_some_and(|unit| unit.is_ascii_digit()) {
214                return Err(None);
215            }
216        } else if self.digits() == 0 {
217            return Err(Some("No number after minus sign in JSON"));
218        }
219        if self.peek() == Some(b'.') {
220            self.at += 1;
221            if self.digits() == 0 {
222                return Err(Some("Unterminated fractional number in JSON"));
223            }
224        }
225        if matches!(self.peek(), Some(b'e' | b'E')) {
226            self.at += 1;
227            if matches!(self.peek(), Some(b'+' | b'-')) {
228                self.at += 1;
229            }
230            if self.digits() == 0 {
231                return Err(Some("Exponent part is missing a number in JSON"));
232            }
233        }
234        Ok(())
235    }
236
237    fn string(&mut self) -> Result<(), V8Failure> {
238        self.at += 1;
239        loop {
240            match self.peek() {
241                None => return Err(Some("Unterminated string in JSON")),
242                Some(b'"') => {
243                    self.at += 1;
244                    return Ok(());
245                }
246                Some(b'\\') => {
247                    self.at += 1;
248                    match self.peek() {
249                        None => return Err(None),
250                        Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => {
251                            self.at += 1
252                        }
253                        Some(b'u') => {
254                            self.at += 1;
255                            for _ in 0..4 {
256                                if !self.peek().is_some_and(|unit| unit.is_ascii_hexdigit()) {
257                                    return Err(Some("Bad Unicode escape in JSON"));
258                                }
259                                self.at += 1;
260                            }
261                        }
262                        Some(_) => return Err(Some("Bad escaped character in JSON")),
263                    }
264                }
265                Some(0..0x20) => {
266                    return Err(Some("Bad control character in string literal in JSON"));
267                }
268                Some(_) => self.at += 1,
269            }
270        }
271    }
272
273    /// V8's message for `failure` at the cursor of a scan of `text`.
274    fn message(&self, text: &str, failure: V8Failure) -> String {
275        let at = self.at;
276        let failure = match (failure, self.peek()) {
277            (Some(message), _) => message,
278            (None, None) => return "Unexpected end of JSON input".into(),
279            (None, Some(b'-' | b'0'..=b'9')) => "Unexpected number in JSON",
280            (None, Some(b'"')) => "Unexpected string in JSON",
281            (None, Some(_))
282                if ["NaN", "Infinity", "undefined", "[object Object]"].contains(&text) =>
283            {
284                return format!("\"{text}\" is not valid JSON");
285            }
286            (None, Some(_)) => {
287                const CONTEXT: usize = 10;
288                let units = self.units;
289                let slice = |from: usize, to: usize| String::from_utf16_lossy(&units[from..to]);
290                let token = slice(at, at + 1);
291                let source = if units.len() <= CONTEXT * 2 {
292                    format!("\"{text}\"")
293                } else if at < CONTEXT {
294                    format!("\"{}\"...", slice(0, at + CONTEXT))
295                } else if at < units.len() - CONTEXT {
296                    format!("...\"{}\"...", slice(at - CONTEXT, at + CONTEXT))
297                } else {
298                    format!("...\"{}\"", slice(at - CONTEXT, units.len()))
299                };
300                return format!("Unexpected token '{token}', {source} is not valid JSON");
301            }
302        };
303        // V8 counts `\r\n`, `\r` and `\n` as one line break each.
304        let mut line = 1;
305        let mut line_start = 0;
306        let mut index = 0;
307        while index < at {
308            if self.units[index] == u16::from(b'\r')
309                && self.units.get(index + 1) == Some(&u16::from(b'\n'))
310                && index + 1 < at
311            {
312                index += 1;
313            }
314            if matches!(u8::try_from(self.units[index]), Ok(b'\r' | b'\n')) {
315                line += 1;
316                line_start = index + 1;
317            }
318            index += 1;
319        }
320        let column = at - line_start + 1;
321        format!("{failure} at position {at} (line {line} column {column})")
322    }
323}
324
325/// Parses possibly incomplete tool-call arguments into an object.
326///
327/// Complete JSON parses as-is (with repair). A prefix of an object yields the
328/// fields seen so far, with a trailing string, number or literal kept in its partial
329/// form. Anything that is not an object yields an empty object.
330pub fn parse_streaming_json(partial: &str) -> Map<String, Value> {
331    if partial.trim().is_empty() {
332        return Map::new();
333    }
334    let value = parse_json_with_repair(partial)
335        .ok()
336        .or_else(|| parse_partial(partial))
337        .or_else(|| parse_partial(&repair_json(partial)));
338    match value {
339        Some(Value::Object(map)) => map,
340        _ => Map::new(),
341    }
342}
343
344/// Parses a JSON prefix, closing whatever is still open.
345fn parse_partial(text: &str) -> Option<Value> {
346    let mut parser = Partial {
347        chars: text.chars().collect(),
348        index: 0,
349        depth: 0,
350    };
351    parser.whitespace();
352    parser.value().map(|(value, _)| value)
353}
354
355struct Partial {
356    chars: Vec<char>,
357    index: usize,
358    /// Objects and arrays open at the cursor.
359    depth: usize,
360}
361
362/// Nesting past this fails the partial parse, as pi's recursive parser fails
363/// when the stack runs out.
364const MAX_PARTIAL_DEPTH: usize = 512;
365
366/// Whether a value ended before its closing token.
367type Complete = bool;
368
369impl Partial {
370    fn peek(&self) -> Option<char> {
371        self.chars.get(self.index).copied()
372    }
373
374    fn whitespace(&mut self) {
375        while self
376            .peek()
377            .is_some_and(|c| matches!(c, ' ' | '\t' | '\n' | '\r'))
378        {
379            self.index += 1;
380        }
381    }
382
383    /// A value, or `None` when nothing usable precedes the end or an error.
384    fn value(&mut self) -> Option<(Value, Complete)> {
385        let next = self.peek()?;
386        if matches!(next, '{' | '[') {
387            if self.depth == MAX_PARTIAL_DEPTH {
388                return None;
389            }
390            self.depth += 1;
391            let value = if next == '{' {
392                self.object()
393            } else {
394                self.array()
395            };
396            self.depth -= 1;
397            return value;
398        }
399        match next {
400            '"' => self
401                .string()
402                .map(|(text, complete)| (Value::String(text), complete)),
403            't' => self.literal("true", Value::Bool(true)),
404            'f' => self.literal("false", Value::Bool(false)),
405            'n' => self.literal("null", Value::Null),
406            '-' | '0'..='9' => self.number(),
407            _ => None,
408        }
409    }
410
411    fn object(&mut self) -> Option<(Value, Complete)> {
412        self.index += 1;
413        let mut map = Map::new();
414        loop {
415            self.whitespace();
416            match self.peek() {
417                None => return Some((Value::Object(map), false)),
418                Some('}') => {
419                    self.index += 1;
420                    return Some((Value::Object(map), true));
421                }
422                Some(',') => {
423                    self.index += 1;
424                    continue;
425                }
426                Some('"') => {}
427                Some(_) => return None,
428            }
429            let (key, complete) = self.string()?;
430            if !complete {
431                return Some((Value::Object(map), false));
432            }
433            self.whitespace();
434            match self.peek() {
435                None => return Some((Value::Object(map), false)),
436                Some(':') => self.index += 1,
437                Some(_) => return None,
438            }
439            self.whitespace();
440            if self.peek().is_none() {
441                return Some((Value::Object(map), false));
442            }
443            match self.value() {
444                Some((value, true)) => {
445                    map.insert(key, value);
446                }
447                Some((value, false)) => {
448                    map.insert(key, value);
449                    return Some((Value::Object(map), false));
450                }
451                None if self.peek().is_none() => return Some((Value::Object(map), false)),
452                None => return None,
453            }
454        }
455    }
456
457    fn array(&mut self) -> Option<(Value, Complete)> {
458        self.index += 1;
459        let mut items = Vec::new();
460        loop {
461            self.whitespace();
462            match self.peek() {
463                None => return Some((Value::Array(items), false)),
464                Some(']') => {
465                    self.index += 1;
466                    return Some((Value::Array(items), true));
467                }
468                Some(',') => {
469                    self.index += 1;
470                    continue;
471                }
472                Some(_) => {}
473            }
474            match self.value() {
475                Some((value, true)) => items.push(value),
476                Some((value, false)) => {
477                    items.push(value);
478                    return Some((Value::Array(items), false));
479                }
480                None if self.peek().is_none() => return Some((Value::Array(items), false)),
481                None => return None,
482            }
483        }
484    }
485
486    fn string(&mut self) -> Option<(String, Complete)> {
487        self.index += 1;
488        let mut text = String::new();
489        while let Some(char) = self.peek() {
490            self.index += 1;
491            match char {
492                '"' => return Some((text, true)),
493                '\\' => {
494                    let Some(escape) = self.peek() else {
495                        return Some((text, false));
496                    };
497                    self.index += 1;
498                    match escape {
499                        '"' | '\\' | '/' => text.push(escape),
500                        'b' => text.push('\u{8}'),
501                        'f' => text.push('\u{c}'),
502                        'n' => text.push('\n'),
503                        'r' => text.push('\r'),
504                        't' => text.push('\t'),
505                        'u' => {
506                            let Some(unit) = self.hex4() else {
507                                return Some((text, false));
508                            };
509                            let code = if (0xd800..0xdc00).contains(&unit)
510                                && self.chars.get(self.index) == Some(&'\\')
511                                && self.chars.get(self.index + 1) == Some(&'u')
512                            {
513                                self.index += 2;
514                                match self.hex4() {
515                                    Some(low) if (0xdc00..0xe000).contains(&low) => {
516                                        0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00)
517                                    }
518                                    _ => 0xfffd,
519                                }
520                            } else {
521                                unit
522                            };
523                            text.push(char::from_u32(code).unwrap_or('\u{fffd}'));
524                        }
525                        _ => return None,
526                    }
527                }
528                _ => text.push(char),
529            }
530        }
531        Some((text, false))
532    }
533
534    fn hex4(&mut self) -> Option<u32> {
535        let digits: String = self.chars.get(self.index..self.index + 4)?.iter().collect();
536        let value = u32::from_str_radix(&digits, 16).ok()?;
537        self.index += 4;
538        Some(value)
539    }
540
541    fn literal(&mut self, word: &str, value: Value) -> Option<(Value, Complete)> {
542        for expected in word.chars() {
543            match self.peek() {
544                None => return Some((value, false)),
545                Some(char) if char == expected => self.index += 1,
546                Some(_) => return None,
547            }
548        }
549        Some((value, true))
550    }
551
552    fn number(&mut self) -> Option<(Value, Complete)> {
553        let start = self.index;
554        while self
555            .peek()
556            .is_some_and(|c| c.is_ascii_digit() || matches!(c, '-' | '+' | '.' | 'e' | 'E'))
557        {
558            self.index += 1;
559        }
560        let complete = self.peek().is_some();
561        let mut text: String = self.chars[start..self.index].iter().collect();
562        loop {
563            if let Ok(number) = serde_json::from_str::<Number>(&text) {
564                return Some((Value::Number(number), complete));
565            }
566            if complete || text.pop().is_none() || text.is_empty() {
567                return None;
568            }
569        }
570    }
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576    use serde_json::json;
577
578    /// Messages recorded from Node 26's `JSON.parse`.
579    #[test]
580    fn v8_parse_errors() {
581        for (text, message) in [
582            ("{\"type\":\"get_state\"}", None),
583            (" [1, 2.5e-3, true, null, \"\\u00e9\"] ", None),
584            ("", Some("Unexpected end of JSON input")),
585            (
586                "not json",
587                Some("Unexpected token 'o', \"not json\" is not valid JSON"),
588            ),
589            (
590                "{\"type\":\"x\"} extra",
591                Some(
592                    "Unexpected non-whitespace character after JSON at position 13 (line 1 column 14)",
593                ),
594            ),
595            (
596                "{",
597                Some("Expected property name or '}' in JSON at position 1 (line 1 column 2)"),
598            ),
599            ("{\"type\":", Some("Unexpected end of JSON input")),
600            (
601                "{\"a\":1,}",
602                Some(
603                    "Expected double-quoted property name in JSON at position 7 (line 1 column 8)",
604                ),
605            ),
606            (
607                "{\"a\" 1}",
608                Some("Expected ':' after property name in JSON at position 5 (line 1 column 6)"),
609            ),
610            (
611                "\"abc",
612                Some("Unterminated string in JSON at position 4 (line 1 column 5)"),
613            ),
614            (
615                "[1 2]",
616                Some(
617                    "Expected ',' or ']' after array element in JSON at position 3 (line 1 column 4)",
618                ),
619            ),
620            (
621                "{\"a\":1 \"b\":2}",
622                Some(
623                    "Expected ',' or '}' after property value in JSON at position 7 (line 1 column 8)",
624                ),
625            ),
626            (
627                "-",
628                Some("No number after minus sign in JSON at position 1 (line 1 column 2)"),
629            ),
630            (
631                "1.",
632                Some("Unterminated fractional number in JSON at position 2 (line 1 column 3)"),
633            ),
634            (
635                "1e+",
636                Some("Exponent part is missing a number in JSON at position 3 (line 1 column 4)"),
637            ),
638            (
639                "\"\\x\"",
640                Some("Bad escaped character in JSON at position 2 (line 1 column 3)"),
641            ),
642            (
643                "\"\\u12g4\"",
644                Some("Bad Unicode escape in JSON at position 5 (line 1 column 6)"),
645            ),
646            (
647                "\"a\tb\"",
648                Some(
649                    "Bad control character in string literal in JSON at position 2 (line 1 column 3)",
650                ),
651            ),
652            ("\"\\", Some("Unexpected end of JSON input")),
653            ("tru", Some("Unexpected end of JSON input")),
654            (
655                "01",
656                Some("Unexpected number in JSON at position 1 (line 1 column 2)"),
657            ),
658            (
659                "[1,]",
660                Some("Unexpected token ']', \"[1,]\" is not valid JSON"),
661            ),
662            ("NaN", Some("\"NaN\" is not valid JSON")),
663            ("undefined", Some("\"undefined\" is not valid JSON")),
664            (
665                "x{\"type\":\"prompt\",\"message\":\"this is long\"}",
666                Some("Unexpected token 'x', \"x{\"type\":\"\"... is not valid JSON"),
667            ),
668            (
669                "{\"type\":\"prompt\",\"message\":\"long enough\"x",
670                Some(
671                    "Expected ',' or '}' after property value in JSON at position 40 (line 1 column 41)",
672                ),
673            ),
674            (
675                "{\"a\":[1,2,3,4,5,6,7,8,9]",
676                Some(
677                    "Expected ',' or '}' after property value in JSON at position 24 (line 1 column 25)",
678                ),
679            ),
680            (
681                "{\"x\":1}\r\n{",
682                Some(
683                    "Unexpected non-whitespace character after JSON at position 9 (line 2 column 1)",
684                ),
685            ),
686            (
687                "\"😀\" x",
688                Some(
689                    "Unexpected non-whitespace character after JSON at position 5 (line 1 column 6)",
690                ),
691            ),
692            ("é", Some("Unexpected token 'é', \"é\" is not valid JSON")),
693            (
694                "{\"type\":\"prompt\",\"message\":x,\"more\":\"stuff\"}",
695                Some("Unexpected token 'x', ...\"\"message\":x,\"more\":\"\"... is not valid JSON"),
696            ),
697            (
698                "{\"type\":\"prompt\",\"message\":x}",
699                Some("Unexpected token 'x', ...\"\"message\":x}\" is not valid JSON"),
700            ),
701        ] {
702            assert_eq!(js_parse_error(text).as_deref(), message, "{text}");
703        }
704        // V8 parses iteratively, so depth alone never fails.
705        let deep = "[".repeat(100_000);
706        assert_eq!(
707            js_parse_error(&deep).as_deref(),
708            Some("Unexpected end of JSON input")
709        );
710        assert_eq!(js_parse_error(&(deep.clone() + &"]".repeat(100_000))), None);
711    }
712
713    #[test]
714    fn repairs_controls_and_escapes() {
715        assert_eq!(repair_json("{\"a\":\"x\ny\"}"), "{\"a\":\"x\\ny\"}");
716        assert_eq!(repair_json(r#"{"a":"C:\dir"}"#), r#"{"a":"C:\\dir"}"#);
717        assert_eq!(repair_json(r#"{"a":"\u00e9\n"}"#), r#"{"a":"\u00e9\n"}"#);
718        assert_eq!(
719            parse_json_with_repair("{\"a\":\"tab\there\"}").unwrap(),
720            json!({"a": "tab\there"})
721        );
722    }
723
724    #[test]
725    fn parses_prefixes() {
726        let cases = [
727            ("", json!({})),
728            ("{", json!({})),
729            (r#"{"pa"#, json!({})),
730            (r#"{"path""#, json!({})),
731            (r#"{"path":"#, json!({})),
732            (r#"{"path":"/tm"#, json!({"path": "/tm"})),
733            (r#"{"path":"/tmp","n":12"#, json!({"path": "/tmp", "n": 12})),
734            (r#"{"n":1.5e"#, json!({"n": 1.5})),
735            (r#"{"a":[1,{"b":tr"#, json!({"a": [1, {"b": true}]})),
736            (r#"{"a":"\u00"#, json!({"a": ""})),
737            (r#"{"a":"x\"#, json!({"a": "x"})),
738            (r#"{"a":1}"#, json!({"a": 1})),
739            ("[1,2]", json!({})),
740            ("nonsense", json!({})),
741        ];
742        for (input, expected) in cases {
743            assert_eq!(
744                Value::Object(parse_streaming_json(input)),
745                expected,
746                "{input}"
747            );
748        }
749        // Too deep for the partial parser, which gives up as pi's does.
750        let nested = |depth: usize| format!("{{\"a\":{}", "[".repeat(depth));
751        assert!(!parse_streaming_json(&nested(MAX_PARTIAL_DEPTH - 1)).is_empty());
752        assert!(parse_streaming_json(&nested(MAX_PARTIAL_DEPTH)).is_empty());
753        assert!(parse_streaming_json(&nested(100_000)).is_empty());
754    }
755}