use std::io::{self, Write};
const ESCAPE_TABLE: [u8; 256] = {
let mut t = [0u8; 256];
let mut i = 0u8;
while i < 0x20 {
t[i as usize] = b'u';
i += 1;
}
t[b'"' as usize] = b'"';
t[b'\\' as usize] = b'\\';
t[b'\n' as usize] = b'n';
t[b'\r' as usize] = b'r';
t[b'\t' as usize] = b't';
t[0x08] = b'b'; t[0x0c] = b'f'; t
};
const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
#[inline]
pub(crate) fn write_escape<W: Write>(w: &mut W, b: u8, esc: u8) -> io::Result<()> {
if esc == b'u' {
w.write_all(&[
b'\\',
b'u',
b'0',
b'0',
HEX_DIGITS[(b >> 4) as usize],
HEX_DIGITS[(b & 0xf) as usize],
])
} else {
w.write_all(&[b'\\', esc])
}
}
pub struct JsonEscapeWriter<W> {
inner: W,
}
impl<W: Write> JsonEscapeWriter<W> {
pub fn new(inner: W) -> Self {
Self { inner }
}
}
impl<W: Write> Write for JsonEscapeWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut start = 0;
for (i, &b) in buf.iter().enumerate() {
let esc = ESCAPE_TABLE[b as usize];
if esc == 0 {
continue;
}
if start < i {
self.inner.write_all(&buf[start..i])?;
}
write_escape(&mut self.inner, b, esc)?;
start = i + 1;
}
if start < buf.len() {
self.inner.write_all(&buf[start..])?;
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
pub(crate) fn write_json_escaped<W: Write>(w: &mut W, data: &str) -> io::Result<()> {
let bytes = data.as_bytes();
let mut start = 0;
for (i, &b) in bytes.iter().enumerate() {
let esc = ESCAPE_TABLE[b as usize];
if esc == 0 {
continue;
}
if start < i {
w.write_all(&bytes[start..i])?;
}
write_escape(w, b, esc)?;
start = i + 1;
}
if start < bytes.len() {
w.write_all(&bytes[start..])?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_escapes() {
let mut buf = Vec::new();
write_json_escaped(&mut buf, "hello \"world\"\nfoo\\bar").unwrap();
assert_eq!(
String::from_utf8(buf).unwrap(),
r#"hello \"world\"\nfoo\\bar"#
);
}
#[test]
fn control_chars() {
let mut buf = Vec::new();
write_json_escaped(&mut buf, "\x00\x1f").unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "\\u0000\\u001f");
}
#[test]
fn writer_adapter() {
let mut buf = Vec::new();
{
let mut ew = JsonEscapeWriter::new(&mut buf);
ew.write_all(b"hello \"world\"\n").unwrap();
}
assert_eq!(String::from_utf8(buf).unwrap(), r#"hello \"world\"\n"#);
}
}