1use std::io::{BufRead, Write};
4
5use lora_executor::{LoraValue, Row};
6use serde_json::Value as J;
7
8use super::format::{
9 invalid_data, row_parse_io_error, RowDecoder, RowEncoder, RowParseError, StreamingRowDecoder,
10};
11use super::value_json::{lora_value_from_json, lora_value_to_json};
12
13pub struct JsonlEncoder<W: Write> {
14 writer: W,
15}
16
17impl<W: Write> JsonlEncoder<W> {
18 pub fn new(writer: W) -> Self {
19 Self { writer }
20 }
21
22 pub fn into_inner(self) -> W {
23 self.writer
24 }
25}
26
27impl<W: Write> RowEncoder for JsonlEncoder<W> {
28 fn begin(&mut self, _columns: &[String]) -> std::io::Result<()> {
29 Ok(())
30 }
31
32 fn write_row(&mut self, row: &Row) -> std::io::Result<()> {
33 let mut obj = serde_json::Map::with_capacity(row.len());
34 for (_, name, value) in row.iter_named() {
35 obj.insert(name.into_owned(), lora_value_to_json(value));
36 }
37 serde_json::to_writer(&mut self.writer, &J::Object(obj))?;
38 self.writer.write_all(b"\n")
39 }
40
41 fn write_named_row(&mut self, columns: &[(String, LoraValue)]) -> std::io::Result<()> {
42 let mut obj = serde_json::Map::with_capacity(columns.len());
43 for (name, value) in columns {
44 obj.insert(name.clone(), lora_value_to_json(value));
45 }
46 serde_json::to_writer(&mut self.writer, &J::Object(obj))?;
47 self.writer.write_all(b"\n")
48 }
49
50 fn finish(&mut self) -> std::io::Result<()> {
51 self.writer.flush()
52 }
53}
54
55pub struct JsonlDecoder<R: BufRead> {
56 reader: R,
57 buf: String,
58}
59
60impl<R: BufRead> JsonlDecoder<R> {
61 pub fn new(reader: R) -> Self {
62 Self {
63 reader,
64 buf: String::new(),
65 }
66 }
67}
68
69impl<R: BufRead> RowDecoder for JsonlDecoder<R> {
70 fn header(&mut self) -> std::io::Result<Option<Vec<String>>> {
71 Ok(None)
72 }
73
74 fn next_row(&mut self) -> std::io::Result<Option<Vec<(String, LoraValue)>>> {
75 loop {
76 self.buf.clear();
77 let read = self.reader.read_line(&mut self.buf)?;
78 if read == 0 {
79 return Ok(None);
80 }
81 let line = self.buf.trim_matches(|c: char| c == '\r' || c == '\n');
82 if line.is_empty() {
83 continue;
84 }
85 let v: J = serde_json::from_str(line).map_err(invalid_data)?;
86 let J::Object(obj) = v else {
87 return Err(invalid_data(format!(
88 "expected JSON object per line, found {}",
89 type_name(&serde_json::from_str(line).unwrap_or(J::Null))
90 )));
91 };
92 let mut out = Vec::with_capacity(obj.len());
93 for (k, raw) in obj {
94 out.push((k, lora_value_from_json(raw).map_err(invalid_data)?));
95 }
96 return Ok(Some(out));
97 }
98 }
99}
100
101fn type_name(v: &J) -> &'static str {
102 match v {
103 J::Null => "null",
104 J::Bool(_) => "bool",
105 J::Number(_) => "number",
106 J::String(_) => "string",
107 J::Array(_) => "array",
108 J::Object(_) => "object",
109 }
110}
111
112pub struct StreamingJsonlDecoder {
116 buffer: Vec<u8>,
120 completed: Vec<Vec<(String, LoraValue)>>,
122 bytes_fed: u64,
123 rows_emitted: u64,
124 record_index: u64,
128 permissive: bool,
129 errors: Vec<RowParseError>,
130}
131
132impl Default for StreamingJsonlDecoder {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl StreamingJsonlDecoder {
139 pub fn new() -> Self {
140 Self {
141 buffer: Vec::with_capacity(64 * 1024),
142 completed: Vec::new(),
143 bytes_fed: 0,
144 rows_emitted: 0,
145 record_index: 0,
146 permissive: false,
147 errors: Vec::new(),
148 }
149 }
150
151 fn parse_line(&mut self, line: &[u8]) -> std::io::Result<()> {
152 let s = std::str::from_utf8(line).map_err(invalid_data)?;
153 let trimmed = s.trim_matches(|c: char| c == '\r' || c == '\n' || c == ' ' || c == '\t');
154 if trimmed.is_empty() {
155 return Ok(());
156 }
157 self.record_index += 1;
158 match parse_jsonl_object(trimmed) {
159 Ok(record) => {
160 self.completed.push(record);
161 self.rows_emitted += 1;
162 Ok(())
163 }
164 Err(message) => self.report_error(message, trimmed),
165 }
166 }
167
168 fn report_error(&mut self, message: String, raw: &str) -> std::io::Result<()> {
169 let err = RowParseError {
170 row: self.record_index,
171 column: None,
172 raw_sample: RowParseError::make_sample(raw),
173 message,
174 };
175 if self.permissive {
176 self.errors.push(err);
177 Ok(())
178 } else {
179 Err(row_parse_io_error(err))
180 }
181 }
182}
183
184fn parse_jsonl_object(line: &str) -> Result<Vec<(String, LoraValue)>, String> {
185 let v: J = serde_json::from_str(line).map_err(|e| e.to_string())?;
186 let J::Object(obj) = v else {
187 return Err(format!(
188 "expected JSON object per line, found {}",
189 type_name(&v)
190 ));
191 };
192 let mut record = Vec::with_capacity(obj.len());
193 for (k, raw) in obj {
194 let value = lora_value_from_json(raw).map_err(|e| format!("key `{k}`: {e}"))?;
195 record.push((k, value));
196 }
197 Ok(record)
198}
199
200impl StreamingRowDecoder for StreamingJsonlDecoder {
201 fn feed(&mut self, chunk: &[u8]) -> std::io::Result<()> {
202 if chunk.is_empty() {
203 return Ok(());
204 }
205 self.bytes_fed += chunk.len() as u64;
206 self.buffer.extend_from_slice(chunk);
207
208 let mut lines: Vec<Vec<u8>> = Vec::new();
214 let mut last_end = 0usize;
215 for (idx, &b) in self.buffer.iter().enumerate() {
216 if b == b'\n' {
217 lines.push(self.buffer[last_end..=idx].to_vec());
218 last_end = idx + 1;
219 }
220 }
221 if last_end > 0 {
222 self.buffer.drain(..last_end);
223 }
224 for line in lines {
225 self.parse_line(&line)?;
226 }
227 Ok(())
228 }
229
230 fn drain(&mut self) -> std::io::Result<Vec<Vec<(String, LoraValue)>>> {
231 Ok(std::mem::take(&mut self.completed))
232 }
233
234 fn finish(&mut self) -> std::io::Result<Vec<Vec<(String, LoraValue)>>> {
235 let leftover = std::mem::take(&mut self.buffer);
239 if !leftover.is_empty() {
240 self.parse_line(&leftover)?;
241 }
242 Ok(std::mem::take(&mut self.completed))
243 }
244
245 fn header(&self) -> Option<&[String]> {
246 None
247 }
248
249 fn bytes_fed(&self) -> u64 {
250 self.bytes_fed
251 }
252
253 fn rows_emitted(&self) -> u64 {
254 self.rows_emitted
255 }
256
257 fn set_permissive(&mut self, on: bool) {
258 self.permissive = on;
259 }
260
261 fn take_errors(&mut self) -> Vec<RowParseError> {
262 std::mem::take(&mut self.errors)
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use std::io::Cursor;
270
271 #[test]
272 fn round_trip_scalars() {
273 let mut buf = Vec::new();
274 {
275 let mut enc = JsonlEncoder::new(&mut buf);
276 enc.begin(&["name".into(), "age".into()]).unwrap();
277 enc.write_named_row(&[
278 ("name".into(), LoraValue::String("alice".into())),
279 ("age".into(), LoraValue::Int(30)),
280 ])
281 .unwrap();
282 enc.write_named_row(&[
283 ("name".into(), LoraValue::String("bob".into())),
284 ("age".into(), LoraValue::Int(25)),
285 ])
286 .unwrap();
287 enc.finish().unwrap();
288 }
289 let text = std::str::from_utf8(&buf).unwrap();
290 let lines: Vec<_> = text.lines().collect();
291 assert_eq!(lines.len(), 2);
292 assert!(lines[0].contains("\"alice\""));
293
294 let mut dec = JsonlDecoder::new(Cursor::new(buf));
297 let r1: std::collections::BTreeMap<_, _> =
298 dec.next_row().unwrap().unwrap().into_iter().collect();
299 assert_eq!(r1.get("name"), Some(&LoraValue::String("alice".into())));
300 assert_eq!(r1.get("age"), Some(&LoraValue::Int(30)));
301 let r2: std::collections::BTreeMap<_, _> =
302 dec.next_row().unwrap().unwrap().into_iter().collect();
303 assert_eq!(r2.get("name"), Some(&LoraValue::String("bob".into())));
304 assert_eq!(r2.get("age"), Some(&LoraValue::Int(25)));
305 assert!(dec.next_row().unwrap().is_none());
306 }
307
308 #[test]
309 fn streaming_split_across_chunks() {
310 let mut dec = StreamingJsonlDecoder::new();
314 dec.feed(b"{\"a\":").unwrap();
315 assert_eq!(dec.drain().unwrap().len(), 0);
316 dec.feed(b"1}\n{\"b\":").unwrap();
317 let rows1 = dec.drain().unwrap();
318 assert_eq!(rows1.len(), 1);
319 assert_eq!(rows1[0][0], ("a".into(), LoraValue::Int(1)));
320 dec.feed(b"2}\n{\"c\":3}").unwrap();
321 let rows2 = dec.drain().unwrap();
322 assert_eq!(rows2.len(), 1);
323 assert_eq!(rows2[0][0], ("b".into(), LoraValue::Int(2)));
324
325 let rows3 = dec.finish().unwrap();
327 assert_eq!(rows3.len(), 1);
328 assert_eq!(rows3[0][0], ("c".into(), LoraValue::Int(3)));
329 assert_eq!(dec.rows_emitted(), 3);
330 }
331
332 #[test]
333 fn streaming_skips_blank_lines() {
334 let mut dec = StreamingJsonlDecoder::new();
335 dec.feed(b"\n\n{\"a\":1}\n\n").unwrap();
336 let rows = dec.drain().unwrap();
337 assert_eq!(rows.len(), 1);
338 assert!(dec.finish().unwrap().is_empty());
339 }
340
341 #[test]
342 fn streaming_strict_mode_bubbles_row_context() {
343 let mut dec = StreamingJsonlDecoder::new();
344 let err = dec.feed(b"{\"a\":1}\nnot json\n").unwrap_err();
345 let parse = super::super::format::downcast_row_parse_error(&err)
346 .expect("error should carry RowParseError");
347 assert_eq!(parse.row, 2);
348 assert!(parse.column.is_none());
349 assert!(parse.raw_sample.contains("not json"));
350 }
351
352 #[test]
353 fn streaming_permissive_mode_skips_and_collects() {
354 let mut dec = StreamingJsonlDecoder::new();
355 dec.set_permissive(true);
356 dec.feed(b"{\"a\":1}\nnot json\n{\"b\":2}\n").unwrap();
357 let rows = dec.drain().unwrap();
358 assert_eq!(rows.len(), 2);
359 assert_eq!(rows[0][0], ("a".into(), LoraValue::Int(1)));
360 assert_eq!(rows[1][0], ("b".into(), LoraValue::Int(2)));
361 let errors = dec.take_errors();
362 assert_eq!(errors.len(), 1);
363 assert_eq!(errors[0].row, 2);
364 assert_eq!(dec.rows_emitted(), 2);
365 assert!(dec.take_errors().is_empty());
367 }
368
369 #[test]
370 fn blank_lines_skipped() {
371 let input = "\n{\"a\":1}\n\n{\"b\":2}\n";
372 let mut dec = JsonlDecoder::new(Cursor::new(input));
373 let r1 = dec.next_row().unwrap().unwrap();
374 assert_eq!(r1[0], ("a".into(), LoraValue::Int(1)));
375 let r2 = dec.next_row().unwrap().unwrap();
376 assert_eq!(r2[0], ("b".into(), LoraValue::Int(2)));
377 assert!(dec.next_row().unwrap().is_none());
378 }
379}