use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use std::sync::Arc;
use minarrow::{Field, SuperTable, Table};
use crate::models::decoders::json::row_decoder::JsonRowDecoder;
use crate::models::decoders::json::builder::ColumnBuilder;
use crate::models::decoders::json::simd::TapeDecoder;
use crate::models::decoders::json::{
JsonDecodeOptions, append_ndjson_line, decode_json, finish_table, make_builders, make_field_map,
};
use crate::models::encoders::json::JsonFormat;
pub struct JsonReader<R: BufRead> {
reader: R,
options: JsonDecodeOptions,
format: JsonFormat,
batch_size: usize,
finished: bool,
decoder: TapeDecoder,
chunk: Vec<u8>,
line: Vec<u8>,
}
impl JsonReader<BufReader<File>> {
pub fn from_path<P: AsRef<Path>>(
path: P,
format: JsonFormat,
options: JsonDecodeOptions,
batch_size: usize,
) -> io::Result<Self> {
let file = File::open(path)?;
Ok(Self::from_reader(
BufReader::new(file),
format,
options,
batch_size,
))
}
}
impl<R: BufRead> JsonReader<R> {
pub fn from_reader(
reader: R,
format: JsonFormat,
options: JsonDecodeOptions,
batch_size: usize,
) -> Self {
JsonReader {
reader,
options,
format,
batch_size,
finished: false,
decoder: TapeDecoder::new(),
chunk: Vec::new(),
line: Vec::with_capacity(4096),
}
}
}
impl<'a> JsonReader<BufReader<&'a [u8]>> {
pub fn from_slice(
slice: &'a [u8],
format: JsonFormat,
options: JsonDecodeOptions,
batch_size: usize,
) -> JsonReader<BufReader<&'a [u8]>> {
JsonReader::from_reader(BufReader::new(slice), format, options, batch_size)
}
}
impl<R: BufRead> JsonReader<R> {
pub fn schema(&self) -> &[Field] {
self.options.schema.as_deref().unwrap_or(&[])
}
pub fn next_batch(&mut self) -> io::Result<Option<Table>> {
if self.finished {
return Ok(None);
}
match self.format {
JsonFormat::Ndjson => self.next_ndjson_batch(),
JsonFormat::Array { .. } => {
let table = decode_json(&mut self.reader, &self.options)?;
self.finished = true;
Ok(Some(table))
}
}
}
pub fn load_table(mut self) -> io::Result<Table> {
match self.format {
JsonFormat::Array { .. } => decode_json(&mut self.reader, &self.options),
JsonFormat::Ndjson => self.drain_ndjson_into_single_table(),
}
}
pub fn load_batched(mut self) -> io::Result<SuperTable> {
let name = match self.format {
JsonFormat::Ndjson => "ndjson",
JsonFormat::Array { .. } => "json",
};
match self.format {
JsonFormat::Array { .. } => {
let table = decode_json(&mut self.reader, &self.options)?;
Ok(SuperTable::from_batches(
vec![Arc::new(table)],
Some(name.into()),
))
}
JsonFormat::Ndjson => {
let mut batches: Vec<Arc<Table>> = Vec::new();
while let Some(table) = self.next_batch()? {
batches.push(Arc::new(table));
}
Ok(SuperTable::from_batches(batches, Some(name.into())))
}
}
}
fn drain_ndjson_into_single_table(&mut self) -> io::Result<Table> {
let schema = self
.options
.schema
.as_ref()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "schema is required"))?
.clone();
let mut builders: Vec<ColumnBuilder> =
make_builders(&schema, 0, self.options.string_bytes_per_row)?;
let field_map = make_field_map(&schema);
loop {
let n = self.fill_chunk()?;
if n == 0 {
break;
}
self.chunk.push(b']');
self.decoder.decode_rows(
self.chunk.as_mut_slice(),
&mut builders,
&field_map,
self.options.on_type_mismatch,
)?;
self.chunk.clear();
}
Ok(finish_table(&schema, builders))
}
fn next_ndjson_batch(&mut self) -> io::Result<Option<Table>> {
let schema = self
.options
.schema
.as_ref()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "schema is required"))?
.clone();
let line_count = self.fill_chunk()?;
if line_count == 0 {
return Ok(None);
}
self.chunk.push(b']');
let mut builders = make_builders(&schema, line_count, self.options.string_bytes_per_row)?;
let field_map = make_field_map(&schema);
self.decoder.decode_rows(
self.chunk.as_mut_slice(),
&mut builders,
&field_map,
self.options.on_type_mismatch,
)?;
self.chunk.clear();
Ok(Some(finish_table(&schema, builders)))
}
fn fill_chunk(&mut self) -> io::Result<usize> {
let mut line_count = 0usize;
while line_count < self.batch_size && self.chunk.len() < self.options.max_chunk_bytes {
self.line.clear();
let n = self.reader.read_until(b'\n', &mut self.line)?;
if n == 0 {
self.finished = true;
break;
}
append_ndjson_line(&self.line, &mut self.chunk, &mut line_count);
}
Ok(line_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use minarrow::{Array, ArrowType, Field, NumericArray};
use std::io::BufReader;
#[test]
fn reader_array_full_table() {
let json = br#"[{"i":1,"s":"a"},{"i":2,"s":"b"},{"i":3,"s":"c"}]"#;
let schema = vec![
Field::new("i", ArrowType::Int32, false, None),
Field::new("s", ArrowType::String, true, None),
];
let reader = JsonReader::<BufReader<&[u8]>>::from_slice(
json,
JsonFormat::default(),
JsonDecodeOptions {
schema: Some(schema),
..Default::default()
},
10,
);
let tbl = reader.load_table().unwrap();
assert_eq!(tbl.n_rows, 3);
assert_eq!(tbl.cols.len(), 2);
}
#[test]
fn reader_ndjson_batches() {
let json = b"{\"i\":1}\n{\"i\":2}\n{\"i\":3}\n{\"i\":4}\n";
let schema = vec![Field::new("i", ArrowType::Int32, false, None)];
let mut reader = JsonReader::<BufReader<&[u8]>>::from_slice(
json,
JsonFormat::Ndjson,
JsonDecodeOptions {
schema: Some(schema),
..Default::default()
},
2,
);
let b1 = reader.next_batch().unwrap().unwrap();
assert_eq!(b1.n_rows, 2);
let b2 = reader.next_batch().unwrap().unwrap();
assert_eq!(b2.n_rows, 2);
let b3 = reader.next_batch().unwrap();
assert!(b3.is_none());
}
#[test]
fn reader_ndjson_full_table() {
let json = b"{\"i\":1}\n{\"i\":2}\n{\"i\":3}\n";
let schema = vec![Field::new("i", ArrowType::Int32, false, None)];
let reader = JsonReader::<BufReader<&[u8]>>::from_slice(
json,
JsonFormat::Ndjson,
JsonDecodeOptions {
schema: Some(schema),
..Default::default()
},
10,
);
let tbl = reader.load_table().unwrap();
assert_eq!(tbl.n_rows, 3);
match &tbl.cols[0].array {
Array::NumericArray(NumericArray::Int32(arr)) => {
let vs: Vec<i32> = arr.data.as_ref().iter().copied().collect();
assert_eq!(vs, vec![1, 2, 3]);
}
_ => panic!("wrong type"),
}
}
#[test]
fn reader_ndjson_short_batch_does_not_finish() {
let json = b"{\"i\":1}\n{\"i\":2}\n{\"i\":3}\n";
let schema = vec![Field::new("i", ArrowType::Int32, false, None)];
let mut reader = JsonReader::<BufReader<&[u8]>>::from_slice(
json,
JsonFormat::Ndjson,
JsonDecodeOptions {
schema: Some(schema),
..Default::default()
},
2,
);
let b1 = reader.next_batch().unwrap().unwrap();
assert_eq!(b1.n_rows, 2);
let b2 = reader.next_batch().unwrap().unwrap();
assert_eq!(b2.n_rows, 1);
let b3 = reader.next_batch().unwrap();
assert!(b3.is_none());
}
}