Lines
94.75 %
Functions
61.29 %
Branches
100 %
#[cfg(feature = "alloc")]
use alloc::string::String;
use core::cmp::Ordering;
use core::fmt::{self, Debug, Display, Formatter, Write};
use core::hash;
use core::str::{CharIndices, Chars};
use crate::anystr::AnyStr;
use crate::error::{Error, ErrorKind};
use crate::parser::{ParseDelegate, Parser};
/// A JSON string.
///
/// Internally, this type may be represented by either an already escaped JSON
/// string or an unescaped string.
/// This type implements `Eq` and `PartialEq` such that the escapes are always
/// handled correctly. Consider the following examples:
/// ```rust
/// use justjson::JsonString;
/// let json = JsonString::from_json(r#""Check out this cat: \"\ud83d\ude39\"""#).unwrap();
/// assert_eq!(json, "Check out this cat: \"😹\"");
/// let alternate_form =
/// JsonString::from_json("\"Check out this cat: \\\"\u{1f639}\\\"\"").unwrap();
/// assert_eq!(json, alternate_form)
/// ```
/// When the underlying representation is an encoded JSON string, the
/// [`JsonStringInfo`] is stored from the parsing operation. This allows the
/// comparison implementations to perform quick length checks on the decoded
/// length to avoid comparing the bytes of strings that decode to different
/// lengths.
/// Additionally, this type implements `Ord`, `PartialOrd`, and `Hash` in ways
/// that are consistent regardless of whether the string has escape sequences or
/// not. If the underlying representation does not need extra processing, the
/// built-in implementations for `&str` are always used.
#[derive(Debug, Eq, Clone)]
pub struct JsonString<'a> {
/// The JSON-source for the string.
pub(crate) source: StringContents<'a>,
pub(crate) info: JsonStringInfo,
}
impl<'a> JsonString<'a> {
/// Parses `json`, expecting a single string value.
/// # Errors
/// Returns [`ErrorKind::ExpectedString`] if a non-string value is
/// encountered.
pub fn from_json(json: &'a str) -> Result<Self, Error> {
Parser::parse_json(json, StringParser).map_err(Error::into_infallable)
/// Returns the contained string after decoding any escape sequences, if
/// needed. If there are no escape sequences, the contents of the string are
/// borrowed.
/// This type keeps track of whether the underlying string contains escape
/// sequences, so this function is nearly instananeous when there are no
/// escape sequences.
#[must_use]
pub fn decode_if_needed(&self) -> AnyStr<'a> {
match &self.source {
StringContents::Json(source) => {
if self.info.has_escapes() {
AnyStr::Owned(self.decoded().collect())
} else {
source.clone()
StringContents::Raw(raw) => raw.clone(),
/// Returns the contained string after escaping any characters, if needed.
/// If the string is already internally represented as a JSON string, this
/// function returns its source via borrowing and no additional processing.
pub fn escape_if_needed(&self) -> AnyStr<'a> {
StringContents::Json(source) => source.clone(),
StringContents::Raw(_) => {
// We promote raw strings with no escapes to Json strings, which
// means we will always need to escape this raw string.
let mut json = String::with_capacity(self.info.expected_length());
json.extend(AsJson::new(&self.source, self.info));
AnyStr::Owned(json)
/// Returns the string after decoding any JSON escape sequences.
pub fn decoded(&self) -> Decoded<'_> {
Decoded::new(&self.source, self.info)
pub fn as_json(&self) -> AsJson<'_> {
AsJson::new(&self.source, self.info)
/// Returns a reference to the underlying storage of this string, if it's
/// already encoded for use in a JSON string. This does not include the
/// surrounding quotation marks.
/// If None is returned, the string must be decoded using
/// [`as_json()`](Self::as_json).
pub fn as_json_str(&self) -> Option<&'_ AnyStr<'a>> {
if let StringContents::Json(source) = &self.source {
Some(source)
None
/// Returns the length of this string when encoded as JSON.
pub fn len(&self) -> usize {
StringContents::Json(json) => json.len(),
StringContents::Raw(_) => self.info.expected_length(),
/// Returns true if this string is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
/// Returns the length of this string when decoded.
pub fn decoded_len(&self) -> usize {
StringContents::Json(_) => self.info.expected_length(),
StringContents::Raw(raw) => raw.len(),
/// Returns a reference to the contents of this value if the contained
/// string does not have any escape sequences that must be decoded.
#[inline]
pub fn as_str(&self) -> Option<&str> {
StringContents::Json(str) if !self.info.has_escapes() => Some(str),
StringContents::Json(_) => None,
StringContents::Raw(str) => Some(str),
impl<'a> From<&'a str> for JsonString<'a> {
fn from(value: &'a str) -> Self {
// Analyze the string for characters that need to be escaped.
let source = StringContents::Raw(AnyStr::Borrowed(value));
let mut escaped = AsJson::new(&source, JsonStringInfo::NONE);
for _ in &mut escaped {}
if escaped.info.has_escapes() {
Self {
info: escaped.info,
source,
// No escapes, we can promote to a JSON string.
source: StringContents::Json(AnyStr::Borrowed(value)),
impl<'a> From<String> for JsonString<'a> {
fn from(value: String) -> Self {
let borrowed_contents = StringContents::Raw(AnyStr::Borrowed(&value));
let mut escaped = AsJson::new(&borrowed_contents, JsonStringInfo::NONE);
source: StringContents::Raw(AnyStr::Owned(value)),
source: StringContents::Json(AnyStr::Owned(value)),
impl<'a, 'b> PartialEq<&'a str> for JsonString<'b> {
fn eq(&self, other: &&'a str) -> bool {
self.eq(*other)
impl<'b> PartialEq<str> for JsonString<'b> {
fn eq(&self, other: &str) -> bool {
// Quick check, if the decoded length differs, the strings can't be equal
if self.info.expected_length() != other.len() {
return false;
.zip(other.chars())
.all(|(a, b)| a == b)
// Direct string comparison excluding the quotes.
source == other
StringContents::Raw(source) => source == other,
impl<'a, 'b> PartialEq<JsonString<'a>> for JsonString<'b> {
fn eq(&self, other: &JsonString<'a>) -> bool {
match (&self.source, &other.source) {
(StringContents::Json(a), StringContents::Json(b)) => {
match (self.info.has_escapes(), other.info.has_escapes()) {
(true, true) => {
if self.info.expected_length() == other.info.expected_length() {
.zip(Decoded::new(&other.source, self.info))
false
(true, false) => self == b.as_ref(),
(false, true) => other == a.as_ref(),
(false, false) => a.as_ref() == b.as_ref(),
(StringContents::Raw(a), StringContents::Raw(b)) => a.as_ref() == b.as_ref(),
(StringContents::Json(_), StringContents::Raw(b)) => {
if other.info.expected_length() == self.len() {
self == b.as_ref()
(StringContents::Raw(a), _) => {
if self.info.expected_length() == other.len() {
other == a.as_ref()
#[test]
fn json_string_eq() {
#[track_caller]
fn test_json<'a, T>(json: &'a str, expected: T)
where
T: Debug,
JsonString<'a>: PartialEq<T>,
{
assert_eq!(JsonString::from_json(json).unwrap(), expected);
fn test_json_ne<'a, T>(json: &'a str, expected: T)
assert_ne!(JsonString::from_json(json).unwrap(), expected);
// Test &str comparisons
test_json(r#""Hello, World!""#, "Hello, World!");
test_json(r#""\"\\\/\b\f\n\r\t\u25eF""#, "\"\\/\x07\x0c\n\r\t\u{25ef}");
test_json("\"\u{25ef}\"", "\u{25ef}");
// Test decoded length not being the same
test_json_ne(r#""\n""#, "");
// Test decoded char not matching
test_json_ne(r#""\n""#, "x");
// Test regular char not matching in decoded comparison
test_json_ne(r#""\na""#, "\nx");
// Test JsonString comparisons
// Decoded length not being the same between two JsonStrings.
test_json_ne(
r#""\u0000""#,
JsonString::from_json(r#""\u0000\u0000""#).unwrap(),
);
// Same decoded length, lhs doesn't have escapes, rhs does
test_json_ne(r#""ab""#, JsonString::from_json(r#""\u0000""#).unwrap());
// Raw strings in JsonString
assert_eq!(JsonString::from("a"), "a");
assert_eq!(JsonString::from("a"), JsonString::from("a"));
assert_eq!(
JsonString::from_json("\"a\"").unwrap(),
JsonString::from("a")
JsonString::from("a"),
JsonString::from_json("\"a\"").unwrap()
assert_eq!(JsonString::from("\0"), "\0");
assert_eq!(JsonString::from("\0"), JsonString::from("\0"));
JsonString::from_json(r#""\u0000""#).unwrap(),
JsonString::from("\0"),
assert_ne!(
JsonString::from("\0 "),
impl<'b> PartialOrd<str> for JsonString<'b> {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
StringContents::Json(_) if self.info.has_escapes() => {
let mut left_chars = self.decoded();
let mut right_chars = other.chars();
loop {
match (left_chars.next(), right_chars.next()) {
(Some(left), Some(right)) => match left.cmp(&right) {
Ordering::Equal => continue,
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
},
(Some(_), _) => return Some(Ordering::Greater),
(_, Some(_)) => return Some(Ordering::Less),
(None, None) => return Some(Ordering::Equal),
StringContents::Json(left) | StringContents::Raw(left) => Some(left.cmp(other)),
impl<'a, 'b> PartialOrd<&'a str> for JsonString<'b> {
fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
self.partial_cmp(*other)
impl<'b> Ord for JsonString<'b> {
fn cmp(&self, other: &Self) -> Ordering {
// There aren't any shortcuts for comparisons
(StringContents::Json(left), StringContents::Json(right))
if !self.info.has_escapes() && !other.info.has_escapes() =>
left.cmp(right)
(StringContents::Raw(left), StringContents::Raw(right)) => left.cmp(right),
(StringContents::Json(_), StringContents::Raw(right)) => {
self.partial_cmp(&&**right).expect("always some")
(StringContents::Raw(left), StringContents::Json(_)) => {
other.partial_cmp(&&**left).expect("always some").reverse()
_ => {
let mut right_chars = other.decoded();
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
(Some(_), _) => return Ordering::Greater,
(_, Some(_)) => return Ordering::Less,
(None, None) => return Ordering::Equal,
impl<'a, 'b> PartialOrd<JsonString<'a>> for JsonString<'b> {
fn partial_cmp(&self, other: &JsonString<'a>) -> Option<Ordering> {
Some(self.cmp(other))
#[cfg(test)]
macro_rules! jstr {
($src:literal) => {
JsonString::from_json(concat!("\"", $src, "\"")).unwrap()
};
#[allow(clippy::cmp_owned)]
fn json_string_cmp() {
macro_rules! assert_lt {
($left:expr, $right:expr) => {
assert!($left < $right);
macro_rules! assert_gt {
assert!($left > $right);
macro_rules! assert_ord_eq {
assert_eq!($left.partial_cmp($right), Some(Ordering::Equal));
// no escapes, which ends up using str.cmp
assert_lt!(jstr!("a"), "b");
assert_gt!(jstr!("b"), "a");
assert_ord_eq!(jstr!("a"), "a");
assert_ord_eq!(JsonString::from("\n"), "\n");
// Escapes vs no escapes. Same as above, but encoding a/b as unicode escapes
assert_lt!(jstr!(r#"\u0061"#), "b");
assert_gt!(jstr!(r#"\u0062"#), "a");
assert_ord_eq!(jstr!(r#"\u0061"#), "a");
// differing lengths, but matching prefix
assert_lt!(jstr!(r#"\u0061"#), "aa");
assert_gt!(jstr!(r#"\u0061a"#), "a");
// Same suite of tests, but with json strings on the right
assert_lt!(jstr!(r#"\u0061"#), JsonString::from("b"));
assert_gt!(jstr!(r#"\u0062"#), JsonString::from("a"));
assert_ord_eq!(jstr!(r#"\u0061"#), &JsonString::from("a"));
assert_lt!(jstr!(r#"\u0061"#), JsonString::from("aa"));
assert_gt!(jstr!(r#"\u0061a"#), JsonString::from("a"));
// Raw on both sides
assert_ord_eq!(JsonString::from("\n"), &JsonString::from("\n"));
// Raw either side
assert_lt!(jstr!(r#"\n"#), JsonString::from("\na"));
assert_gt!(JsonString::from("\na"), jstr!(r#"\n"#));
// JSON no escapes both sides
assert_lt!(jstr!("a"), JsonString::from("b"));
impl<'a> hash::Hash for JsonString<'a> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// The standard string hash writes out as bytes, but char
// hashing is done by converting to u32. We're purposely
// re-implementing Hasher::write_str via chars.
let mut char_bytes = [0; 4];
for ch in self.decoded() {
state.write(ch.encode_utf8(&mut char_bytes).as_bytes());
state.write_u8(0xff); // from Hasher::write_str
StringContents::Json(str) | StringContents::Raw(str) => str.hash(state),
#[cfg(feature = "std")]
fn json_string_hash() {
use core::hash::{BuildHasher, Hasher};
use std::collections::hash_map::RandomState;
fn hash(t: impl hash::Hash, state: &RandomState) -> u64 {
let mut hasher = state.build_hasher();
t.hash(&mut hasher);
hasher.finish()
let state = RandomState::default();
hash(JsonString::from("\n"), &state),
hash(jstr!("\\n"), &state)
assert_eq!(hash(jstr!("a"), &state), hash("a", &state));
impl<'a> Display for JsonString<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.decoded().fmt(f)
fn display() {
use std::string::ToString;
let json = JsonString::from_json(r#""hello, world!""#).unwrap();
assert_eq!(json.to_string(), "hello, world!");
fn json_string_from_json() {
JsonString::from_json(r#""Hello, World!""#).unwrap(),
JsonString {
source: StringContents::Json(AnyStr::Borrowed("Hello, World!")),
info: JsonStringInfo::new(false, 13),
let expected_string = JsonString::from_json("true")
.expect_err("shouldn't allow non-strings")
.kind;
assert!(matches!(expected_string, ErrorKind::ExpectedString));
fn as_str() {
let json_with_escapes = JsonString::from_json(r#""\n""#).unwrap();
assert_eq!(json_with_escapes.as_str(), None);
let json_no_escapes = JsonString::from_json(r#""hi""#).unwrap();
assert_eq!(json_no_escapes.as_str(), Some("hi"));
let raw = JsonString::from("hi");
assert_eq!(raw.as_str(), Some("hi"));
fn json_string_from_raw() {
assert_eq!(JsonString::from(String::from("a")), JsonString::from("a"));
fn decode_if_needed() {
let empty = JsonString::from_json(r#""""#).unwrap();
let AnyStr::Borrowed(string) = empty.decode_if_needed() else {
unreachable!()
assert_eq!(string, "");
let has_escapes = JsonString::from_json(r#""\r""#).unwrap();
let AnyStr::Owned(string) = has_escapes.decode_if_needed() else {
assert_eq!(string, "\r");
let decoded_via_display = alloc::format!("{}", has_escapes.decoded());
assert_eq!(decoded_via_display, "\r");
let raw_string = JsonString::from("raw string");
let AnyStr::Borrowed(string) = raw_string.decode_if_needed() else {
assert_eq!(string, "raw string");
let decoded_via_display = alloc::format!("{}", raw_string.decoded());
assert_eq!(decoded_via_display, "raw string");
/// Information about a parsed [`JsonString`].
/// This type stores:
/// - Whether any escape sequences are in the source
/// - The length of the String if the escape sequences are decoded.
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub struct JsonStringInfo(usize);
impl JsonStringInfo {
const HAS_ESCAPES_MASK: usize = usize::MAX ^ Self::UNESCAPED_BYTES_MASK;
/// Information with no escapes and 0 length.
pub const NONE: Self = Self::new(false, 0);
#[cfg(target_pointer_width = "32")]
const UNESCAPED_BYTES_MASK: usize = 0x7FFF_FFFF;
#[cfg(target_pointer_width = "64")]
const UNESCAPED_BYTES_MASK: usize = 0x7FFF_FFFF_FFFF_FFFF;
/// Returns a new instance with the initial values provided.
pub const fn new(has_escapes: bool, unescaped_length: usize) -> Self {
// Panics aren't allowed in const fns yet.
// assert_eq!(
// unescaped_length & Self::HAS_ESCAPES_MASK,
// 0,
// "too many bytes needed"
// );
if has_escapes {
Self(Self::HAS_ESCAPES_MASK | unescaped_length)
Self(unescaped_length)
/// Returns whether the string has any escape sequences.
pub const fn has_escapes(self) -> bool {
self.0 & Self::HAS_ESCAPES_MASK != 0
/// Returns the length of the string after decoding any escape sequences.
pub const fn expected_length(self) -> usize {
self.0 & Self::UNESCAPED_BYTES_MASK
pub(crate) fn add_bytes(&mut self, bytes: usize) {
self.0 = self.0.checked_add(bytes).expect("too many extra bytes");
pub(crate) fn add_bytes_from_escape(&mut self, bytes: usize) {
self.0 = (Self::HAS_ESCAPES_MASK | self.0)
.checked_add(bytes)
.expect("too many extra bytes");
impl fmt::Debug for JsonStringInfo {
f.debug_struct("EscapeInfo")
.field("has_escapes", &self.has_escapes())
.field("unescaped_length", &self.expected_length())
.finish()
fn test_string_info_debug() {
let mut info = JsonStringInfo::NONE;
info.add_bytes_from_escape(1);
alloc::format!("{info:?}"),
"EscapeInfo { has_escapes: true, unescaped_length: 1 }"
#[derive(Clone)]
pub struct Decoded<'a> {
needs_decoding: bool,
source: &'a str,
chars: CharIndices<'a>,
impl<'a> Decoded<'a> {
fn new(source: &'a StringContents<'a>, info: JsonStringInfo) -> Self {
match source {
StringContents::Json(source) => Self {
needs_decoding: info.has_escapes(),
chars: source.char_indices(),
StringContents::Raw(source) => Self {
needs_decoding: false,
impl<'a> Iterator for Decoded<'a> {
type Item = char;
#[allow(unsafe_code)]
fn next(&mut self) -> Option<Self::Item> {
let (_, ch) = self.chars.next()?;
if self.needs_decoding && ch == '\\' {
match self.chars.next().expect("already validated") {
(_, 'r') => Some('\r'),
(_, 'n') => Some('\n'),
(_, 't') => Some('\t'),
(_, 'b') => Some('\x07'),
(_, 'f') => Some('\x0c'),
(offset, 'u') => {
// four hex digits
for _ in 0..4 {
self.chars.next();
// We access the string slice directly rather than
// trying to rebuild via char indicies for speed.
let hex = &self.source[offset + 1..offset + 5];
let mut codepoint = u32::from_str_radix(hex, 16).expect("already validated");
if (HIGH_SURROGATE_MIN..=HIGH_SURROGATE_MAX).contains(&codepoint) {
// Surrogate pair, which has already been validated.
// Process another \uxxxx.
for _ in 0..6 {
let hex = &self.source[offset + 7..offset + 11];
let second_codepoint =
u32::from_str_radix(hex, 16).expect("already validated");
codepoint = merge_surrogate_pair(codepoint, second_codepoint);
// SAFETY: The JSON string has already had its UTF
// escapes validated.
Some(unsafe { char::from_u32_unchecked(codepoint) })
(_, other) => Some(other),
Some(ch)
impl<'a> Display for Decoded<'a> {
for ch in self.clone() {
f.write_char(ch)?;
Ok(())
pub struct AsJson<'a> {
chars: Chars<'a>,
state: EscapeState,
info: JsonStringInfo,
impl<'a> Display for AsJson<'a> {
enum EscapeState {
AlreadyEscaped,
None,
Single(u8),
Unicode {
current_nibble: Option<u8>,
codepoint: u16,
impl<'b, 'a> AsJson<'b> {
fn new(value: &'b StringContents<'a>, info: JsonStringInfo) -> Self {
match value {
chars: source.chars(),
state: EscapeState::AlreadyEscaped,
info,
state: EscapeState::None,
impl<'a> Iterator for AsJson<'a> {
match &mut self.state {
EscapeState::AlreadyEscaped => self.chars.next(),
EscapeState::None => {
let ch = self.chars.next()?;
match u8::try_from(ch) {
Ok(b) if SAFE_STRING_BYTES[b as usize] => {
self.info.add_bytes(1);
Ok(b'"' | b'\\' | b'/') => {
self.state = EscapeState::Single(ch as u8);
self.info.add_bytes_from_escape(2);
Some('\\')
Ok(b'\x07') => {
self.state = EscapeState::Single(b'b');
Ok(b'\n') => {
self.state = EscapeState::Single(b'n');
Ok(b'\r') => {
self.state = EscapeState::Single(b'r');
Ok(b'\t') => {
self.state = EscapeState::Single(b't');
Ok(b'\x0c') => {
self.state = EscapeState::Single(b'f');
Ok(b) if (0..=31).contains(&b) => {
// Another control character, but it's not a common one.
// This must be encoded as a unicode escape.
self.state = EscapeState::Unicode {
current_nibble: None,
codepoint: u16::from(b),
self.info.add_bytes_from_escape(6);
Err(_) => {
// A unicode character
self.info.add_bytes(ch.len_utf8());
_ => unreachable!("SAFE_CHARACTERS prevents these"),
EscapeState::Single(ch) => {
let ch = *ch;
self.state = EscapeState::None;
Some(ch as char)
EscapeState::Unicode {
current_nibble,
codepoint,
} => match current_nibble {
Some(current_nibble) => {
#[allow(clippy::cast_possible_truncation)]
let bits = *codepoint as u8 & 0xF;
*codepoint >>= 4;
let ch = if bits < 10 { bits + b'0' } else { bits + b'a' };
*current_nibble += 1;
if *current_nibble == 4 {
None => {
*current_nibble = Some(0);
Some('u')
fn size_hint(&self) -> (usize, Option<usize>) {
self.chars.size_hint()
#[allow(clippy::inconsistent_digit_grouping)]
pub(crate) static HEX_OFFSET_TABLE: [u8; 256] = {
const ERR: u8 = u8::MAX;
[
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 0
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 1
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 2
0__, 1__, 2__, 3__, 4__, 5__, 6__, 7__, 8__, 9__, ERR, ERR, ERR, ERR, ERR, ERR, // 3
ERR, 10_, 11_, 12_, 13_, 14_, 15_, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 4
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 5
ERR, 10_, 11_, 12_, 13_, 14_, 15_, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 6
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 7
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 8
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // 9
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // A
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // B
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // C
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // D
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // E
ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, // F
]
pub(crate) static SAFE_STRING_BYTES: &[bool; 256] = {
const ER: bool = false;
const UC: bool = true;
const BS: bool = false;
const QU: bool = false;
const __: bool = true;
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
&[
ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, // 0
ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, ER, // 1
__, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4
__, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // 8
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // 9
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // A
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // B
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // C
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // D
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // E
UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, UC, // F
pub(crate) static SAFE_STRING_BYTES_VERIFY_UTF8: &[bool; 256] = {
const UC: bool = false;
fn is_empty() {
assert!(JsonString::from("").is_empty());
assert!(JsonString::from_json("\"\"").unwrap().is_empty());
assert!(!JsonString::from(" ").is_empty());
assert!(!JsonString::from_json("\" \"").unwrap().is_empty());
fn escape() {
let original = "\"\\/\u{07}\t\n\r\u{0c}\u{0}\u{25ef}";
let escaped = "\\\"\\\\/\\b\\t\\n\\r\\f\\u0000\u{25ef}";
let raw = JsonString::from(original);
assert_eq!(raw.len(), escaped.len());
assert_eq!(raw.decoded_len(), original.len());
assert_eq!(raw, original);
let decoded = raw.decoded().collect::<String>();
assert_eq!(decoded, original);
let decoded = raw.decode_if_needed();
let json = raw.as_json().collect::<String>();
assert_eq!(json, escaped);
let json = raw.escape_if_needed();
assert!(raw.as_json_str().is_none());
// Test converting a String that needs encoding.
let raw: JsonString<'static> = JsonString::from(String::from(original));
// Test with a raw string that doesn't need encoding
let original = "hello";
let raw = JsonString::from("hello");
assert_eq!(raw.len(), original.len());
assert_eq!(json, original);
assert_eq!(raw.as_json_str().map(AnyStr::as_ref), Some("hello"));
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum StringContents<'a> {
Json(AnyStr<'a>),
Raw(AnyStr<'a>),
pub(crate) const HIGH_SURROGATE_MIN: u32 = 0xD800;
pub(crate) const HIGH_SURROGATE_MAX: u32 = 0xDBFF;
pub(crate) const LOW_SURROGATE_MIN: u32 = 0xDC00;
pub(crate) const LOW_SURROGATE_MAX: u32 = 0xDFFF;
pub(crate) const DECODED_SURROGATE_BASE: u32 = 0x1_0000;
pub(crate) fn merge_surrogate_pair(high: u32, low: u32) -> u32 {
DECODED_SURROGATE_BASE | ((high - HIGH_SURROGATE_MIN) * 0x400) | (low - LOW_SURROGATE_MIN)
struct StringParser;
impl<'a> ParseDelegate<'a> for StringParser {
type Array = ();
type Error = ErrorKind;
type Key = ();
type Object = ();
type Value = JsonString<'a>;
fn null(&mut self) -> Result<Self::Value, Self::Error> {
Err(ErrorKind::ExpectedString)
fn boolean(&mut self, _value: bool) -> Result<Self::Value, Self::Error> {
fn number(&mut self, _value: crate::JsonNumber<'a>) -> Result<Self::Value, Self::Error> {
fn string(&mut self, value: JsonString<'a>) -> Result<Self::Value, Self::Error> {
Ok(value)
fn begin_object(&mut self) -> Result<Self::Object, Self::Error> {
fn object_key(
&mut self,
_object: &mut Self::Object,
_key: crate::JsonString<'a>,
) -> Result<Self::Key, Self::Error> {
unreachable!("error returned from begin_object")
fn object_value(
_key: Self::Key,
_value: Self::Value,
) -> Result<(), Self::Error> {
fn object_is_empty(&self, _object: &Self::Object) -> bool {
fn end_object(&mut self, _object: Self::Object) -> Result<Self::Value, Self::Error> {
fn begin_array(&mut self) -> Result<Self::Array, Self::Error> {
fn array_value(
_array: &mut Self::Array,
unreachable!("error returned from array_value")
fn array_is_empty(&self, _array: &Self::Array) -> bool {
fn end_array(&mut self, _array: Self::Array) -> Result<Self::Value, Self::Error> {
fn kind_of(&self, _value: &Self::Value) -> crate::parser::JsonKind {
unreachable!("allow_all_types_at_root is always true")
fn from_json_bad_types() {
JsonString::from_json("1").unwrap_err().kind,
ErrorKind::ExpectedString
JsonString::from_json("null").unwrap_err().kind,
JsonString::from_json("true").unwrap_err().kind,
JsonString::from_json("[]").unwrap_err().kind,
JsonString::from_json("{}").unwrap_err().kind,