Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,5 @@ raw_value = []
# overflow the stack after deserialization has completed, including, but not
# limited to, Display and Debug and Drop impls.
unbounded_depth = []

partial_parsing = []
137 changes: 129 additions & 8 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub struct Deserializer<R> {
single_precision: bool,
#[cfg(feature = "unbounded_depth")]
disable_recursion_limit: bool,
#[cfg(feature = "partial_parsing")]
allow_partial_list: bool,
#[cfg(feature = "partial_parsing")]
allow_partial_object: bool,
#[cfg(feature = "partial_parsing")]
allow_partial_string: bool,
}

impl<'de, R> Deserializer<R>
Expand Down Expand Up @@ -65,6 +71,12 @@ where
single_precision: false,
#[cfg(feature = "unbounded_depth")]
disable_recursion_limit: false,
#[cfg(feature = "partial_parsing")]
allow_partial_list: false,
#[cfg(feature = "partial_parsing")]
allow_partial_object: false,
#[cfg(feature = "partial_parsing")]
allow_partial_string: false,
}
}
}
Expand Down Expand Up @@ -216,6 +228,77 @@ impl<'de, R: Read<'de>> Deserializer<R> {
self.disable_recursion_limit = true;
}

/// Allows lists to be partial without resulting in an EOF error.
///
/// # Examples
///
/// ```
/// use serde::Deserialize;
/// use serde_json::{Value, json};
///
/// fn main() {
/// let json = r#"["test", "list""#;
///
/// let mut deserializer = serde_json::Deserializer::from_str(&json);
/// deserializer.allow_partial_list();
/// let value = Value::deserialize(&mut deserializer).unwrap();
/// assert_eq!(value, json!(["test", "list"]));
/// }
/// ```
#[cfg(feature = "partial_parsing")]
#[cfg_attr(docsrs, doc(cfg(feature = "partial_parsing")))]
pub fn allow_partial_list(&mut self) {
self.allow_partial_list = true;
}

/// Allows objects to be partial without resulting in an EOF error.
///
/// # Examples
///
/// ```
/// use serde::Deserialize;
/// use serde_json::{Value, json};
///
/// fn main() {
/// let json = r#"{"test": "value""#;
///
/// let mut deserializer = serde_json::Deserializer::from_str(&json);
/// deserializer.allow_partial_object();
/// let value = Value::deserialize(&mut deserializer).unwrap();
/// assert_eq!(value, json!({"test": "value"}));
/// }
/// ```
#[cfg(feature = "partial_parsing")]
#[cfg_attr(docsrs, doc(cfg(feature = "partial_parsing")))]
pub fn allow_partial_object(&mut self) {
self.allow_partial_object = true;
}

/// Allows strings to be partial without resulting in an EOF error.
///
/// # Examples
///
/// ```
/// use serde::Deserialize;
/// use serde_json::{Value, json};
///
/// fn main() {
/// // Note that the quote is part of Rust's syntax not of our JSON data.
/// let json = r#"{"test": "value"#;
///
/// let mut deserializer = serde_json::Deserializer::from_str(&json);
/// deserializer.allow_partial_object();
/// deserializer.allow_partial_string();
/// let value = Value::deserialize(&mut deserializer).unwrap();
/// assert_eq!(value, json!({"test": "value"}));
/// }
/// ```
#[cfg(feature = "partial_parsing")]
#[cfg_attr(docsrs, doc(cfg(feature = "partial_parsing")))]
pub fn allow_partial_string(&mut self) {
self.allow_partial_string = true;
}

pub(crate) fn peek(&mut self) -> Result<Option<u8>> {
self.read.peek()
}
Expand Down Expand Up @@ -303,7 +386,11 @@ impl<'de, R: Read<'de>> Deserializer<R> {
b'"' => {
self.eat_char();
self.scratch.clear();
match self.read.parse_str(&mut self.scratch) {
match self.read.parse_str(
&mut self.scratch,
#[cfg(feature = "partial_parsing")]
self.allow_partial_string,
) {
Ok(s) => de::Error::invalid_type(Unexpected::Str(&s), exp),
Err(err) => return err,
}
Expand Down Expand Up @@ -1081,6 +1168,8 @@ impl<'de, R: Read<'de>> Deserializer<R> {
}
}
Some(_) => Err(self.peek_error(ErrorCode::TrailingCharacters)),
#[cfg(feature = "partial_parsing")]
None if self.allow_partial_list => Ok(()),
None => Err(self.peek_error(ErrorCode::EofWhileParsingList)),
}
}
Expand All @@ -1093,6 +1182,8 @@ impl<'de, R: Read<'de>> Deserializer<R> {
}
Some(b',') => Err(self.peek_error(ErrorCode::TrailingComma)),
Some(_) => Err(self.peek_error(ErrorCode::TrailingCharacters)),
#[cfg(feature = "partial_parsing")]
None if self.allow_partial_object => Ok(()),
None => Err(self.peek_error(ErrorCode::EofWhileParsingObject)),
}
}
Expand Down Expand Up @@ -1136,7 +1227,10 @@ impl<'de, R: Read<'de>> Deserializer<R> {
}
b'"' => {
self.eat_char();
tri!(self.read.ignore_str());
tri!(self.read.ignore_str(
#[cfg(feature = "partial_parsing")]
self.allow_partial_string
));
None
}
frame @ (b'[' | b'{') => {
Expand Down Expand Up @@ -1200,7 +1294,10 @@ impl<'de, R: Read<'de>> Deserializer<R> {
Some(_) => return Err(self.peek_error(ErrorCode::KeyMustBeAString)),
None => return Err(self.peek_error(ErrorCode::EofWhileParsingObject)),
}
tri!(self.read.ignore_str());
tri!(self.read.ignore_str(
#[cfg(feature = "partial_parsing")]
self.allow_partial_string
));
match tri!(self.parse_whitespace()) {
Some(b':') => self.eat_char(),
Some(_) => return Err(self.peek_error(ErrorCode::ExpectedColon)),
Expand Down Expand Up @@ -1423,7 +1520,11 @@ impl<'de, R: Read<'de>> de::Deserializer<'de> for &mut Deserializer<R> {
b'"' => {
self.eat_char();
self.scratch.clear();
match tri!(self.read.parse_str(&mut self.scratch)) {
match tri!(self.read.parse_str(
&mut self.scratch,
#[cfg(feature = "partial_parsing")]
self.allow_partial_string
)) {
Reference::Borrowed(s) => visitor.visit_borrowed_str(s),
Reference::Copied(s) => visitor.visit_str(s),
}
Expand Down Expand Up @@ -1534,7 +1635,11 @@ impl<'de, R: Read<'de>> de::Deserializer<'de> for &mut Deserializer<R> {
b'"' => {
self.eat_char();
self.scratch.clear();
match tri!(self.read.parse_str(&mut self.scratch)) {
match tri!(self.read.parse_str(
&mut self.scratch,
#[cfg(feature = "partial_parsing")]
self.allow_partial_string
)) {
Reference::Borrowed(s) => visitor.visit_borrowed_str(s),
Reference::Copied(s) => visitor.visit_str(s),
}
Expand Down Expand Up @@ -1643,7 +1748,11 @@ impl<'de, R: Read<'de>> de::Deserializer<'de> for &mut Deserializer<R> {
b'"' => {
self.eat_char();
self.scratch.clear();
match tri!(self.read.parse_str_raw(&mut self.scratch)) {
match tri!(self.read.parse_str_raw(
&mut self.scratch,
#[cfg(feature = "partial_parsing")]
self.allow_partial_string
)) {
Reference::Borrowed(b) => visitor.visit_borrowed_bytes(b),
Reference::Copied(b) => visitor.visit_bytes(b),
}
Expand Down Expand Up @@ -1937,6 +2046,8 @@ impl<'de, 'a, R: Read<'de> + 'a> de::SeqAccess<'de> for SeqAccess<'a, R> {
) -> Result<bool> {
let peek = match tri!(seq.de.parse_whitespace()) {
Some(b) => b,
#[cfg(feature = "partial_parsing")]
None if seq.de.allow_partial_list => return Ok(false),
None => {
return Err(seq.de.peek_error(ErrorCode::EofWhileParsingList));
}
Expand Down Expand Up @@ -1988,6 +2099,8 @@ impl<'de, 'a, R: Read<'de> + 'a> de::MapAccess<'de> for MapAccess<'a, R> {
fn has_next_key<'de, 'a, R: Read<'de> + 'a>(map: &mut MapAccess<'a, R>) -> Result<bool> {
let peek = match tri!(map.de.parse_whitespace()) {
Some(b) => b,
#[cfg(feature = "partial_parsing")]
None if map.de.allow_partial_object => return Ok(false),
None => {
return Err(map.de.peek_error(ErrorCode::EofWhileParsingObject));
}
Expand Down Expand Up @@ -2206,7 +2319,11 @@ where
{
self.de.eat_char();
self.de.scratch.clear();
match tri!(self.de.read.parse_str(&mut self.de.scratch)) {
match tri!(self.de.read.parse_str(
&mut self.de.scratch,
#[cfg(feature = "partial_parsing")]
self.de.allow_partial_string
)) {
Reference::Borrowed(s) => visitor.visit_borrowed_str(s),
Reference::Copied(s) => visitor.visit_str(s),
}
Expand Down Expand Up @@ -2252,7 +2369,11 @@ where
}
_ => {
self.de.scratch.clear();
let s = tri!(self.de.read.parse_str(&mut self.de.scratch));
let s = tri!(self.de.read.parse_str(
&mut self.de.scratch,
#[cfg(feature = "partial_parsing")]
self.de.allow_partial_string
));
Err(de::Error::invalid_type(Unexpected::Str(&s), &visitor))
}
};
Expand Down
Loading