use alloc::string::String;
use alloc::vec::Vec;
use crate::error::{MalformedJson, TruncatedJson};
#[derive(Debug, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct JsonSplitter {
buf: Vec<u8>,
depth: usize,
in_string: bool,
escaped: bool,
in_value: bool,
strict: bool,
values_emitted: usize,
error: Option<MalformedJson>,
}
impl JsonSplitter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn strict() -> Self {
Self {
strict: true,
..Self::default()
}
}
#[must_use]
pub fn error(&self) -> Option<&MalformedJson> {
self.error.as_ref()
}
pub fn feed(&mut self, chunk: &[u8], out: &mut Vec<String>) {
for &b in chunk {
self.push_byte(b, out);
}
}
#[must_use]
pub fn has_partial(&self) -> bool {
!self.buf.is_empty()
}
pub fn finish(&mut self, out: &mut Vec<String>) -> Result<(), FinishError> {
if let Some(err) = self.error.take() {
self.reset();
return Err(FinishError::Malformed(err));
}
if self.in_value && self.depth == 0 && !self.in_string {
self.emit(out);
return Ok(());
}
let buffered = self.buf.len();
self.reset();
if buffered > 0 {
Err(FinishError::Truncated(TruncatedJson { buffered }))
} else {
Ok(())
}
}
fn reset(&mut self) {
self.buf.clear();
self.depth = 0;
self.in_string = false;
self.escaped = false;
self.in_value = false;
}
fn push_byte(&mut self, b: u8, out: &mut Vec<String>) {
if self.in_value && self.in_string {
self.buf.push(b);
if self.escaped {
self.escaped = false;
} else if b == b'\\' {
self.escaped = true;
} else if b == b'"' {
self.in_string = false;
}
return;
}
let is_sep = b.is_ascii_whitespace() || b == b',';
if !self.in_value {
if is_sep {
return;
}
self.in_value = true;
} else if self.depth == 0 && is_sep {
self.emit(out);
return;
}
self.buf.push(b);
match b {
b'"' => self.in_string = true,
b'{' | b'[' => self.depth += 1,
b'}' | b']' => {
if self.depth == 0 {
if self.strict {
if self.error.is_none() {
self.error = Some(MalformedJson {
byte: b,
values_emitted: self.values_emitted,
});
}
self.buf.pop();
self.in_value = false;
return;
}
self.emit(out);
return;
}
self.depth -= 1;
if self.depth == 0 {
self.emit(out);
}
}
_ => { }
}
}
fn emit(&mut self, out: &mut Vec<String>) {
let bytes = core::mem::take(&mut self.buf);
out.push(String::from_utf8_lossy(&bytes).into_owned());
self.values_emitted += 1;
self.reset();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FinishError {
Truncated(TruncatedJson),
Malformed(MalformedJson),
}
impl core::fmt::Display for FinishError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Truncated(e) => core::fmt::Display::fmt(e, f),
Self::Malformed(e) => core::fmt::Display::fmt(e, f),
}
}
}
impl From<TruncatedJson> for FinishError {
fn from(e: TruncatedJson) -> Self {
Self::Truncated(e)
}
}
impl From<MalformedJson> for FinishError {
fn from(e: MalformedJson) -> Self {
Self::Malformed(e)
}
}
#[cfg(feature = "std")]
impl std::error::Error for FinishError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Truncated(e) => Some(e),
Self::Malformed(e) => Some(e),
}
}
}