use std::convert::Infallible;
use std::{fmt, io, str};
use serde::Serialize;
use serde_json::ser::{CompactFormatter, PrettyFormatter, Serializer};
use super::AsIndent;
use crate::ascii_str::{AsciiChar, AsciiStr};
use crate::{FastWritable, NO_VALUES, Values};
#[inline]
pub fn json(value: impl Serialize) -> Result<impl fmt::Display, Infallible> {
Ok(ToJson { value })
}
#[inline]
pub fn json_pretty(
value: impl Serialize,
indent: impl AsIndent,
) -> Result<impl fmt::Display, Infallible> {
Ok(ToJsonPretty { value, indent })
}
#[derive(Debug, Clone)]
struct ToJson<S> {
value: S,
}
#[derive(Debug, Clone)]
struct ToJsonPretty<S, I> {
value: S,
indent: I,
}
impl<S: Serialize> FastWritable for ToJson<S> {
#[inline]
fn write_into(&self, f: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
serialize(f, &self.value, CompactFormatter)
}
}
impl<S: Serialize> fmt::Display for ToJson<S> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(self.write_into(f, NO_VALUES)?)
}
}
impl<S: Serialize, I: AsIndent> FastWritable for ToJsonPretty<S, I> {
#[inline]
fn write_into(&self, f: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
serialize(
f,
&self.value,
PrettyFormatter::with_indent(self.indent.as_indent().as_bytes()),
)
}
}
impl<S: Serialize, I: AsIndent> fmt::Display for ToJsonPretty<S, I> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(self.write_into(f, NO_VALUES)?)
}
}
fn serialize<S, W, F>(dest: &mut W, value: &S, formatter: F) -> Result<(), crate::Error>
where
S: Serialize + ?Sized,
W: fmt::Write + ?Sized,
F: serde_json::ser::Formatter,
{
struct JsonWriter<'a, W: fmt::Write + ?Sized>(&'a mut W);
impl<W: fmt::Write + ?Sized> io::Write for JsonWriter<'_, W> {
#[inline]
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.write_all(bytes)?;
Ok(bytes.len())
}
fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
write_escaped_str(&mut *self.0, string)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[inline]
fn write_escaped_str(dest: &mut (impl fmt::Write + ?Sized), src: &str) -> fmt::Result {
let mut escaped_buf = ESCAPED_BUF_INIT;
let mut last = 0;
for (index, byte) in src.bytes().enumerate() {
if let Some(escaped) = get_escaped(byte) {
[escaped_buf[4], escaped_buf[5]] = escaped;
write_str_if_nonempty(dest, &src[last..index])?;
dest.write_str(AsciiStr::from_slice(&escaped_buf[..ESCAPED_BUF_LEN]))?;
last = index + 1;
}
}
write_str_if_nonempty(dest, &src[last..])
}
let mut serializer = Serializer::with_formatter(JsonWriter(dest), formatter);
Ok(value.serialize(&mut serializer)?)
}
#[inline]
fn get_escaped(byte: u8) -> Option<[AsciiChar; 2]> {
const _: () = assert!(CHAR_RANGE < 32);
if let MIN_CHAR..=MAX_CHAR = byte
&& (1u32 << (byte - MIN_CHAR)) & BITS != 0
{
return Some(TABLE.0[(byte - MIN_CHAR) as usize]);
}
None
}
#[inline(always)]
fn write_str_if_nonempty(output: &mut (impl fmt::Write + ?Sized), input: &str) -> fmt::Result {
if !input.is_empty() {
output.write_str(input)
} else {
Ok(())
}
}
const CHARS: &[u8] = br#"&'<>"#;
const MIN_CHAR: u8 = {
let mut v = u8::MAX;
let mut i = 0;
while i < CHARS.len() {
if v > CHARS[i] {
v = CHARS[i];
}
i += 1;
}
v
};
const MAX_CHAR: u8 = {
let mut v = u8::MIN;
let mut i = 0;
while i < CHARS.len() {
if v < CHARS[i] {
v = CHARS[i];
}
i += 1;
}
v
};
const BITS: u32 = {
let mut bits = 0;
let mut i = 0;
while i < CHARS.len() {
bits |= 1 << (CHARS[i] - MIN_CHAR);
i += 1;
}
bits
};
const CHAR_RANGE: usize = (MAX_CHAR - MIN_CHAR + 1) as usize;
#[repr(align(64))]
struct Table([[AsciiChar; 2]; CHAR_RANGE]);
const TABLE: &Table = &{
let mut table = Table([UNESCAPED; CHAR_RANGE]);
let mut i = 0;
while i < CHARS.len() {
let c = CHARS[i];
table.0[c as u32 as usize - MIN_CHAR as usize] = AsciiChar::two_hex_digits(c as u32);
i += 1;
}
table
};
const UNESCAPED: [AsciiChar; 2] = AsciiStr::new_sized("");
const ESCAPED_BUF_INIT_UNPADDED: &str = "\\u00__";
const ESCAPED_BUF_INIT: [AsciiChar; 8] = AsciiStr::new_sized(ESCAPED_BUF_INIT_UNPADDED);
const ESCAPED_BUF_LEN: usize = ESCAPED_BUF_INIT_UNPADDED.len();
#[cfg(all(test, feature = "alloc"))]
mod tests {
use alloc::string::ToString;
use alloc::vec;
use super::*;
#[test]
fn test_ugly() {
assert_eq!(json(true).unwrap().to_string(), "true");
assert_eq!(json("foo").unwrap().to_string(), r#""foo""#);
assert_eq!(json(true).unwrap().to_string(), "true");
assert_eq!(json("foo").unwrap().to_string(), r#""foo""#);
assert_eq!(
json("<script>").unwrap().to_string(),
r#""\u003cscript\u003e""#
);
assert_eq!(
json(vec!["foo", "bar"]).unwrap().to_string(),
r#"["foo","bar"]"#
);
}
#[test]
fn test_pretty() {
assert_eq!(json_pretty(true, "").unwrap().to_string(), "true");
assert_eq!(
json_pretty("<script>", "").unwrap().to_string(),
r#""\u003cscript\u003e""#
);
assert_eq!(
json_pretty(vec!["foo", "bar"], "").unwrap().to_string(),
r#"[
"foo",
"bar"
]"#
);
assert_eq!(
json_pretty(vec!["foo", "bar"], 2).unwrap().to_string(),
r#"[
"foo",
"bar"
]"#
);
assert_eq!(
json_pretty(vec!["foo", "bar"], "————").unwrap().to_string(),
r#"[
————"foo",
————"bar"
]"#
);
}
}