Skip to main content

json_streamparse_rs/
lib.rs

1//! # json-streamparse-rs
2//!
3//! Streaming JSON balance detector. Feed bytes incrementally and ask
4//! whether the buffer currently holds a complete top-level JSON value.
5//!
6//! This is *not* a full parser; it's the small utility you want when an
7//! LLM is producing JSON token-by-token and you need to know "can I
8//! hand this to `serde_json::from_str` yet?" without actually parsing
9//! every prefix.
10//!
11//! String-aware (won't be fooled by `{` inside a string literal),
12//! escape-aware (`\\\"` doesn't end the string).
13//!
14//! ## Example
15//!
16//! ```
17//! use json_streamparse_rs::Balancer;
18//! let mut b = Balancer::new();
19//! b.push(b"{\"name\":\"Cl");
20//! assert!(!b.complete());
21//! b.push(b"aude\",\"v\":1}");
22//! assert!(b.complete());
23//! ```
24
25#![deny(missing_docs)]
26
27/// Streaming JSON balance detector.
28#[derive(Debug, Default, Clone)]
29pub struct Balancer {
30    depth: i32,
31    started: bool,
32    in_string: bool,
33    escape: bool,
34    bytes_consumed: u64,
35}
36
37impl Balancer {
38    /// Empty detector.
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    /// Feed bytes. Updates internal state in place.
44    pub fn push(&mut self, bytes: &[u8]) {
45        for &b in bytes {
46            self.bytes_consumed += 1;
47            if self.in_string {
48                if self.escape {
49                    self.escape = false;
50                } else if b == b'\\' {
51                    self.escape = true;
52                } else if b == b'"' {
53                    self.in_string = false;
54                }
55                continue;
56            }
57            match b {
58                b'{' | b'[' => {
59                    self.depth += 1;
60                    self.started = true;
61                }
62                b'}' | b']' => {
63                    if self.depth > 0 {
64                        self.depth -= 1;
65                    }
66                }
67                b'"' => {
68                    self.in_string = true;
69                    self.started = true;
70                }
71                b' ' | b'\t' | b'\n' | b'\r' => {}
72                _ => {
73                    self.started = true;
74                }
75            }
76        }
77    }
78
79    /// True when the input so far is non-empty and bracket-balanced
80    /// (depth = 0) and not currently mid-string.
81    pub fn complete(&self) -> bool {
82        self.started && self.depth == 0 && !self.in_string
83    }
84
85    /// Current bracket depth (0 at the root).
86    pub fn depth(&self) -> i32 {
87        self.depth
88    }
89
90    /// Bytes consumed so far.
91    pub fn bytes_consumed(&self) -> u64 {
92        self.bytes_consumed
93    }
94
95    /// Reset to empty.
96    pub fn reset(&mut self) {
97        *self = Self::new();
98    }
99}