Skip to main content

vexy_json_core/streaming/
ndjson.rs

1// this_file: src/streaming/ndjson.rs
2
3//! Newline-Delimited JSON (NDJSON) support for streaming parser.
4//!
5//! NDJSON is a format where each line is a valid JSON value, allowing
6//! for streaming of multiple JSON objects without wrapping them in an array.
7
8use super::{StreamingEvent, StreamingParser};
9use crate::ast::Value;
10use crate::error::{Error, Result};
11use crate::parser::ParserOptions;
12
13/// Parser for Newline-Delimited JSON streams
14pub struct NdJsonParser {
15    /// Line buffer
16    line_buffer: String,
17    /// Parser options
18    #[allow(dead_code)]
19    options: ParserOptions,
20    /// Whether we've reached the end
21    finished: bool,
22    /// Current line number for error reporting
23    line_number: usize,
24}
25
26impl NdJsonParser {
27    /// Create a new NDJSON parser with default options
28    pub fn new() -> Self {
29        Self::with_options(ParserOptions::default())
30    }
31
32    /// Create a new NDJSON parser with custom options
33    pub fn with_options(options: ParserOptions) -> Self {
34        Self {
35            line_buffer: String::new(),
36            options,
37            finished: false,
38            line_number: 1,
39        }
40    }
41
42    /// Feed a chunk of input to the parser
43    pub fn feed(&mut self, chunk: &str) -> Result<Vec<Value>> {
44        if self.finished {
45            return Err(Error::Custom("Parser already finished".to_string()));
46        }
47
48        let mut results = Vec::new();
49
50        for ch in chunk.chars() {
51            if ch == '\n' {
52                // Process complete line
53                if !self.line_buffer.trim().is_empty() {
54                    match self.parse_line(&self.line_buffer) {
55                        Ok(value) => results.push(value),
56                        Err(e) => {
57                            return Err(Error::Custom(format!(
58                                "Error on line {}: {}",
59                                self.line_number, e
60                            )));
61                        }
62                    }
63                }
64                self.line_buffer.clear();
65                self.line_number += 1;
66            } else {
67                self.line_buffer.push(ch);
68            }
69        }
70
71        Ok(results)
72    }
73    
74    /// Finish parsing and return any remaining values
75    pub fn finish(&mut self) -> Result<Vec<Value>> {
76        if self.finished {
77            return Ok(Vec::new());
78        }
79        
80        let mut results = Vec::new();
81        
82        // Process any remaining data in the buffer
83        if !self.line_buffer.trim().is_empty() {
84            match self.parse_line(&self.line_buffer) {
85                Ok(value) => results.push(value),
86                Err(e) => {
87                    return Err(Error::Custom(format!(
88                        "Error on line {}: {}",
89                        self.line_number, e
90                    )));
91                }
92            }
93        }
94        
95        self.finished = true;
96        Ok(results)
97    }
98
99    /// Parse a single line as JSON
100    fn parse_line(&self, line: &str) -> Result<Value> {
101        let trimmed = line.trim();
102        if trimmed.is_empty() {
103            return Err(Error::Custom("Empty line".to_string()));
104        }
105
106        // Use the regular parser for the line
107        #[cfg(feature = "serde")]
108        {
109            crate::parse_with_options(trimmed, self.options.clone())
110        }
111        #[cfg(not(feature = "serde"))]
112        {
113            crate::parse(trimmed)
114        }
115    }
116
117
118    /// Check if the parser has finished
119    #[inline(always)]
120    pub fn is_finished(&self) -> bool {
121        self.finished
122    }
123
124    /// Get the current line number
125    #[inline(always)]
126    pub fn line_number(&self) -> usize {
127        self.line_number
128    }
129}
130
131/// Streaming NDJSON parser that emits events for each line
132pub struct StreamingNdJsonParser {
133    /// Line buffer
134    line_buffer: String,
135    /// Event queue for current line
136    event_queue: Vec<StreamingEvent>,
137    /// Parser options
138    options: ParserOptions,
139    /// Whether we've reached the end
140    finished: bool,
141    /// Current line number
142    line_number: usize,
143}
144
145impl StreamingNdJsonParser {
146    /// Create a new streaming NDJSON parser
147    pub fn new() -> Self {
148        Self::with_options(ParserOptions::default())
149    }
150
151    /// Create a new streaming NDJSON parser with custom options
152    pub fn with_options(options: ParserOptions) -> Self {
153        Self {
154            line_buffer: String::new(),
155            event_queue: Vec::new(),
156            options,
157            finished: false,
158            line_number: 1,
159        }
160    }
161
162    /// Feed a chunk of input
163    pub fn feed(&mut self, chunk: &str) -> Result<()> {
164        if self.finished {
165            return Err(Error::Custom("Parser already finished".to_string()));
166        }
167
168        for ch in chunk.chars() {
169            if ch == '\n' {
170                // Process complete line
171                if !self.line_buffer.trim().is_empty() {
172                    self.start_line_parsing()?;
173                }
174                self.line_buffer.clear();
175                self.line_number += 1;
176            } else {
177                self.line_buffer.push(ch);
178            }
179        }
180
181        Ok(())
182    }
183
184    /// Start parsing a complete line
185    fn start_line_parsing(&mut self) -> Result<()> {
186        let mut parser = StreamingParser::with_options(self.options.clone());
187        parser.feed(&self.line_buffer)?;
188        // Don't call finish - each line is complete JSON
189
190        // Collect all events from this line
191        let mut line_events = Vec::new();
192        while let Some(event) = parser.next_event()? {
193            if matches!(event, StreamingEvent::EndOfInput) {
194                break;
195            }
196            line_events.push(event);
197        }
198
199        // Add line separator event
200        line_events.push(StreamingEvent::EndOfInput);
201
202        self.event_queue.extend(line_events);
203        Ok(())
204    }
205
206    /// Get the next event
207    pub fn next_event(&mut self) -> Result<Option<StreamingEvent>> {
208        if !self.event_queue.is_empty() {
209            Ok(Some(self.event_queue.remove(0)))
210        } else {
211            Ok(None)
212        }
213    }
214
215    /// Signal end of input
216    pub fn finish(&mut self) -> Result<()> {
217        if !self.line_buffer.trim().is_empty() {
218            self.start_line_parsing()?;
219        }
220        self.finished = true;
221        Ok(())
222    }
223
224    /// Check if the parser has finished
225    pub fn is_finished(&self) -> bool {
226        self.finished && self.event_queue.is_empty()
227    }
228
229    /// Get the current line number
230    #[inline(always)]
231    pub fn line_number(&self) -> usize {
232        self.line_number
233    }
234}
235
236/// Iterator interface for NDJSON values
237pub struct NdJsonIterator {
238    parser: NdJsonParser,
239    buffer: String,
240    input: Box<dyn Iterator<Item = std::result::Result<String, std::io::Error>>>,
241}
242
243impl NdJsonIterator {
244    /// Create a new NDJSON iterator from a line iterator
245    pub fn new<I>(input: I) -> Self
246    where
247        I: Iterator<Item = std::result::Result<String, std::io::Error>> + 'static,
248    {
249        Self {
250            parser: NdJsonParser::new(),
251            buffer: String::new(),
252            input: Box::new(input),
253        }
254    }
255
256    /// Create a new NDJSON iterator with custom options
257    pub fn with_options<I>(input: I, options: ParserOptions) -> Self
258    where
259        I: Iterator<Item = std::result::Result<String, std::io::Error>> + 'static,
260    {
261        Self {
262            parser: NdJsonParser::with_options(options),
263            buffer: String::new(),
264            input: Box::new(input),
265        }
266    }
267}
268
269impl Iterator for NdJsonIterator {
270    type Item = Result<Value>;
271
272    fn next(&mut self) -> Option<Self::Item> {
273        loop {
274            match self.input.next() {
275                Some(Ok(line)) => {
276                    self.buffer = line;
277                    self.buffer.push('\n');
278
279                    match self.parser.feed(&self.buffer) {
280                        Ok(values) => {
281                            if !values.is_empty() {
282                                return Some(Ok(values.into_iter().next().unwrap()));
283                            }
284                        }
285                        Err(e) => return Some(Err(e)),
286                    }
287                }
288                Some(Err(e)) => {
289                    return Some(Err(Error::Custom(format!("IO error: {e}"))));
290                }
291                None => {
292                    // End of input
293                    match self.parser.finish() {
294                        Ok(values) => {
295                            if !values.is_empty() {
296                                return Some(Ok(values.into_iter().next().unwrap()));
297                            }
298                            return None;
299                        }
300                        Err(e) => return Some(Err(e)),
301                    }
302                }
303            }
304        }
305    }
306}
307
308impl Default for NdJsonParser {
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314impl Default for StreamingNdJsonParser {
315    fn default() -> Self {
316        Self::new()
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn test_ndjson_parser() {
326        let mut parser = NdJsonParser::new();
327
328        let input = r#"{"name": "Alice", "age": 30}
329{"name": "Bob", "age": 25}
330{"name": "Charlie", "age": 35}"#;
331
332        let mut values = parser.feed(input).unwrap();
333        values.extend(parser.finish().unwrap());
334        assert_eq!(values.len(), 3);
335
336        // Check first value
337        if let Value::Object(obj) = &values[0] {
338            assert_eq!(
339                obj.get("name").unwrap(),
340                &Value::String("Alice".to_string())
341            );
342        } else {
343            panic!("Expected object");
344        }
345    }
346
347    #[test]
348    fn test_streaming_ndjson() {
349        let mut parser = StreamingNdJsonParser::new();
350
351        parser
352            .feed(
353                r#"{"key": "value1"}
354{"key": "value2"}"#,
355            )
356            .unwrap();
357        parser.finish().unwrap();
358
359        let mut events = Vec::new();
360        while let Some(event) = parser.next_event().unwrap() {
361            events.push(event);
362        }
363
364        // Should have events for two complete JSON objects
365        assert!(!events.is_empty());
366    }
367
368    #[test]
369    fn test_empty_lines() {
370        let mut parser = NdJsonParser::new();
371
372        let input = r#"{"valid": true}
373
374{"also": "valid"}"#;
375
376        let mut values = parser.feed(input).unwrap();
377        values.extend(parser.finish().unwrap());
378        assert_eq!(values.len(), 2);
379    }
380}