extern crate alloc;
use alloc::{string::String, vec::Vec};
use facet_core::Facet;
use facet_format::{FormatSerializer, ScalarValue, SerializeError, serialize_root};
use facet_reflect::Peek;
#[derive(Debug, Clone)]
pub struct SerializeOptions {
pub pretty: bool,
pub indent: &'static str,
pub bytes_format: BytesFormat,
}
impl Default for SerializeOptions {
fn default() -> Self {
Self {
pretty: false,
indent: " ",
bytes_format: BytesFormat::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BytesFormat {
#[default]
Array,
Hex(HexBytesOptions),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HexBytesOptions {
pub truncate_above: Option<usize>,
pub head: usize,
pub tail: usize,
}
impl Default for HexBytesOptions {
fn default() -> Self {
Self::new()
}
}
impl HexBytesOptions {
pub const fn new() -> Self {
Self {
truncate_above: None,
head: 32,
tail: 32,
}
}
pub const fn truncate(mut self, truncate_above: usize) -> Self {
self.truncate_above = Some(truncate_above);
self
}
pub const fn head_tail(mut self, head: usize, tail: usize) -> Self {
self.head = head;
self.tail = tail;
self
}
}
impl SerializeOptions {
pub fn new() -> Self {
Self::default()
}
pub const fn pretty(mut self) -> Self {
self.pretty = true;
self
}
pub const fn indent(mut self, indent: &'static str) -> Self {
self.indent = indent;
self.pretty = true;
self
}
pub const fn bytes_format(mut self, bytes_format: BytesFormat) -> Self {
self.bytes_format = bytes_format;
self
}
pub const fn bytes_as_array(mut self) -> Self {
self.bytes_format = BytesFormat::Array;
self
}
pub const fn bytes_as_hex(mut self) -> Self {
self.bytes_format = BytesFormat::Hex(HexBytesOptions::new());
self
}
pub const fn bytes_as_hex_with_options(mut self, options: HexBytesOptions) -> Self {
self.bytes_format = BytesFormat::Hex(options);
self
}
}
#[derive(Debug)]
pub struct JsonSerializeError {
msg: &'static str,
}
impl core::fmt::Display for JsonSerializeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.msg)
}
}
impl std::error::Error for JsonSerializeError {}
#[derive(Debug, Clone, Copy)]
enum Ctx {
Struct { first: bool },
Seq { first: bool },
}
pub struct JsonSerializer {
out: Vec<u8>,
stack: Vec<Ctx>,
options: SerializeOptions,
}
impl JsonSerializer {
pub fn new() -> Self {
Self::with_options(SerializeOptions::default())
}
pub const fn with_options(options: SerializeOptions) -> Self {
Self {
out: Vec::new(),
stack: Vec::new(),
options,
}
}
pub fn finish(self) -> Vec<u8> {
self.out
}
const fn depth(&self) -> usize {
self.stack.len()
}
fn write_indent(&mut self) {
if self.options.pretty {
self.out.push(b'\n');
for _ in 0..self.depth() {
self.out.extend_from_slice(self.options.indent.as_bytes());
}
}
}
fn before_value(&mut self) -> Result<(), JsonSerializeError> {
match self.stack.last_mut() {
Some(Ctx::Seq { first }) => {
if !*first {
self.out.push(b',');
}
*first = false;
self.write_indent();
}
Some(Ctx::Struct { .. }) => {
}
None => {}
}
Ok(())
}
fn write_json_string(&mut self, s: &str) {
const STEP_SIZE: usize = 16; type Chunk = [u8; STEP_SIZE];
self.out.push(b'"');
let mut s = s;
while let Some(Ok(chunk)) = s.as_bytes().get(..STEP_SIZE).map(Chunk::try_from) {
let window = u128::from_ne_bytes(chunk);
let completely_ascii = window & 0x80808080808080808080808080808080 == 0;
let quote_free = !contains_byte(window, 0x22);
let backslash_free = !contains_byte(window, 0x5c);
let control_char_free = no_control_chars(window);
if completely_ascii && quote_free && backslash_free && control_char_free {
self.out.extend_from_slice(&chunk);
s = &s[STEP_SIZE..];
} else {
let mut chars = s.chars();
let mut count = STEP_SIZE;
for c in &mut chars {
self.write_json_escaped_char(c);
count = count.saturating_sub(c.len_utf8());
if count == 0 {
break;
}
}
s = chars.as_str();
}
}
for c in s.chars() {
self.write_json_escaped_char(c);
}
self.out.push(b'"');
}
#[inline]
fn write_json_escaped_char(&mut self, c: char) {
match c {
'"' => self.out.extend_from_slice(b"\\\""),
'\\' => self.out.extend_from_slice(b"\\\\"),
'\n' => self.out.extend_from_slice(b"\\n"),
'\r' => self.out.extend_from_slice(b"\\r"),
'\t' => self.out.extend_from_slice(b"\\t"),
'\u{08}' => self.out.extend_from_slice(b"\\b"),
'\u{0C}' => self.out.extend_from_slice(b"\\f"),
c if c.is_ascii_control() => {
let code_point = c as u32;
let to_hex = |d: u32| {
if d < 10 {
b'0' + d as u8
} else {
b'a' + (d - 10) as u8
}
};
let buf = [
b'\\',
b'u',
to_hex((code_point >> 12) & 0xF),
to_hex((code_point >> 8) & 0xF),
to_hex((code_point >> 4) & 0xF),
to_hex(code_point & 0xF),
];
self.out.extend_from_slice(&buf);
}
c if c.is_ascii() => {
self.out.push(c as u8);
}
c => {
let mut buf = [0u8; 4];
let len = c.encode_utf8(&mut buf).len();
self.out.extend_from_slice(&buf[..len]);
}
}
}
#[inline]
fn write_hex_byte(&mut self, byte: u8) {
let hi = byte >> 4;
let lo = byte & 0x0f;
self.out
.push(if hi < 10 { b'0' + hi } else { b'a' + (hi - 10) });
self.out
.push(if lo < 10 { b'0' + lo } else { b'a' + (lo - 10) });
}
#[inline]
fn write_u8_decimal(&mut self, value: u8) {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(value.to_string().as_bytes());
}
fn write_bytes_array(&mut self, bytes: &[u8]) {
self.out.push(b'[');
for (index, byte) in bytes.iter().copied().enumerate() {
if index != 0 {
self.out.push(b',');
}
self.write_u8_decimal(byte);
}
self.out.push(b']');
}
fn write_bytes_hex(&mut self, bytes: &[u8], options: HexBytesOptions) {
self.out.push(b'"');
self.out.extend_from_slice(b"0x");
let should_truncate = options
.truncate_above
.is_some_and(|max_len| bytes.len() > max_len);
if !should_truncate {
for byte in bytes {
self.write_hex_byte(*byte);
}
self.out.push(b'"');
return;
}
let head = options.head.min(bytes.len());
let max_tail = bytes.len().saturating_sub(head);
let tail = options.tail.min(max_tail);
for byte in &bytes[..head] {
self.write_hex_byte(*byte);
}
let omitted = bytes.len().saturating_sub(head + tail);
self.out.extend_from_slice(b"..<");
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(omitted).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(omitted.to_string().as_bytes());
self.out.extend_from_slice(b" bytes>..");
if tail != 0 {
for byte in &bytes[bytes.len() - tail..] {
self.write_hex_byte(*byte);
}
}
self.out.push(b'"');
}
fn write_bytes_with_options(&mut self, bytes: &[u8]) {
match self.options.bytes_format {
BytesFormat::Array => self.write_bytes_array(bytes),
BytesFormat::Hex(options) => self.write_bytes_hex(bytes, options),
}
}
}
#[inline]
const fn contains_byte(val: u128, byte: u8) -> bool {
let mask = 0x01010101010101010101010101010101u128 * (byte as u128);
let xor_result = val ^ mask;
let has_zero = (xor_result.wrapping_sub(0x01010101010101010101010101010101))
& !xor_result
& 0x80808080808080808080808080808080;
has_zero != 0
}
#[inline]
const fn no_control_chars(value: u128) -> bool {
let masked = value & 0xe0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0;
let has_zero = (masked.wrapping_sub(0x01010101010101010101010101010101))
& !masked
& 0x80808080808080808080808080808080;
has_zero == 0
}
impl Default for JsonSerializer {
fn default() -> Self {
Self::new()
}
}
impl FormatSerializer for JsonSerializer {
type Error = JsonSerializeError;
fn begin_struct(&mut self) -> Result<(), Self::Error> {
self.before_value()?;
self.out.push(b'{');
self.stack.push(Ctx::Struct { first: true });
Ok(())
}
fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
match self.stack.last_mut() {
Some(Ctx::Struct { first }) => {
if !*first {
self.out.push(b',');
}
*first = false;
self.write_indent();
self.write_json_string(key);
self.out.push(b':');
if self.options.pretty {
self.out.push(b' ');
}
Ok(())
}
_ => Err(JsonSerializeError {
msg: "field_key called outside of a struct",
}),
}
}
fn end_struct(&mut self) -> Result<(), Self::Error> {
match self.stack.pop() {
Some(Ctx::Struct { first }) => {
if !first {
self.write_indent();
}
self.out.push(b'}');
Ok(())
}
_ => Err(JsonSerializeError {
msg: "end_struct called without matching begin_struct",
}),
}
}
fn begin_seq(&mut self) -> Result<(), Self::Error> {
self.before_value()?;
self.out.push(b'[');
self.stack.push(Ctx::Seq { first: true });
Ok(())
}
fn end_seq(&mut self) -> Result<(), Self::Error> {
match self.stack.pop() {
Some(Ctx::Seq { first }) => {
if !first {
self.write_indent();
}
self.out.push(b']');
Ok(())
}
_ => Err(JsonSerializeError {
msg: "end_seq called without matching begin_seq",
}),
}
}
fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
self.before_value()?;
match scalar {
ScalarValue::Null | ScalarValue::Unit => self.out.extend_from_slice(b"null"),
ScalarValue::Bool(v) => {
if v {
self.out.extend_from_slice(b"true")
} else {
self.out.extend_from_slice(b"false")
}
}
ScalarValue::Char(c) => {
self.out.push(b'"');
self.write_json_escaped_char(c);
self.out.push(b'"');
}
ScalarValue::I64(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::U64(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::I128(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::U128(v) => {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
ScalarValue::F64(v) => {
if v.is_nan() || v.is_infinite() {
self.out.extend_from_slice(b"null");
} else {
#[cfg(feature = "fast")]
self.out
.extend_from_slice(zmij::Buffer::new().format(v).as_bytes());
#[cfg(not(feature = "fast"))]
self.out.extend_from_slice(v.to_string().as_bytes());
}
}
ScalarValue::Str(s) => self.write_json_string(&s),
ScalarValue::Bytes(bytes) => self.write_bytes_with_options(bytes.as_ref()),
}
Ok(())
}
fn serialize_byte_sequence(&mut self, bytes: &[u8]) -> Result<bool, Self::Error> {
self.before_value()?;
self.write_bytes_with_options(bytes);
Ok(true)
}
fn serialize_byte_array(&mut self, bytes: &[u8]) -> Result<bool, Self::Error> {
self.serialize_byte_sequence(bytes)
}
fn raw_serialize_shape(&self) -> Option<&'static facet_core::Shape> {
Some(crate::RawJson::SHAPE)
}
fn raw_scalar(&mut self, content: &str) -> Result<(), Self::Error> {
self.before_value()?;
self.out.extend_from_slice(content.as_bytes());
Ok(())
}
fn format_namespace(&self) -> Option<&'static str> {
Some("json")
}
}
pub fn to_vec<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
to_vec_with_options(value, &SerializeOptions::default())
}
pub fn to_vec_pretty<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
to_vec_with_options(value, &SerializeOptions::default().pretty())
}
pub fn to_vec_with_options<'facet, T>(
value: &'_ T,
options: &SerializeOptions,
) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
let mut serializer = JsonSerializer::with_options(options.clone());
serialize_root(&mut serializer, Peek::new(value))?;
Ok(serializer.finish())
}
pub fn to_string<'facet, T>(value: &'_ T) -> Result<String, SerializeError<JsonSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
let bytes = to_vec(value)?;
Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
}
pub fn to_string_pretty<'facet, T>(
value: &'_ T,
) -> Result<String, SerializeError<JsonSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
let bytes = to_vec_pretty(value)?;
Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
}
pub fn to_string_with_options<'facet, T>(
value: &'_ T,
options: &SerializeOptions,
) -> Result<String, SerializeError<JsonSerializeError>>
where
T: Facet<'facet> + ?Sized,
{
let bytes = to_vec_with_options(value, options)?;
Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
}
pub fn peek_to_string<'input, 'facet>(
peek: Peek<'input, 'facet>,
) -> Result<String, SerializeError<JsonSerializeError>> {
peek_to_string_with_options(peek, &SerializeOptions::default())
}
pub fn peek_to_string_pretty<'input, 'facet>(
peek: Peek<'input, 'facet>,
) -> Result<String, SerializeError<JsonSerializeError>> {
peek_to_string_with_options(peek, &SerializeOptions::default().pretty())
}
pub fn peek_to_string_with_options<'input, 'facet>(
peek: Peek<'input, 'facet>,
options: &SerializeOptions,
) -> Result<String, SerializeError<JsonSerializeError>> {
let mut serializer = JsonSerializer::with_options(options.clone());
serialize_root(&mut serializer, peek)?;
let bytes = serializer.finish();
Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
}
pub fn to_writer_std<'facet, W, T>(writer: W, value: &T) -> std::io::Result<()>
where
W: std::io::Write,
T: Facet<'facet> + ?Sized,
{
peek_to_writer_std(writer, Peek::new(value))
}
pub fn to_writer_std_pretty<'facet, W, T>(writer: W, value: &T) -> std::io::Result<()>
where
W: std::io::Write,
T: Facet<'facet> + ?Sized,
{
peek_to_writer_std_pretty(writer, Peek::new(value))
}
pub fn to_writer_std_with_options<'facet, W, T>(
writer: W,
value: &T,
options: &SerializeOptions,
) -> std::io::Result<()>
where
W: std::io::Write,
T: Facet<'facet> + ?Sized,
{
peek_to_writer_std_with_options(writer, Peek::new(value), options)
}
pub fn peek_to_writer_std<'input, 'facet, W>(
writer: W,
peek: Peek<'input, 'facet>,
) -> std::io::Result<()>
where
W: std::io::Write,
{
peek_to_writer_std_with_options(writer, peek, &SerializeOptions::default())
}
pub fn peek_to_writer_std_pretty<'input, 'facet, W>(
writer: W,
peek: Peek<'input, 'facet>,
) -> std::io::Result<()>
where
W: std::io::Write,
{
peek_to_writer_std_with_options(writer, peek, &SerializeOptions::default().pretty())
}
pub fn peek_to_writer_std_with_options<'input, 'facet, W>(
mut writer: W,
peek: Peek<'input, 'facet>,
options: &SerializeOptions,
) -> std::io::Result<()>
where
W: std::io::Write,
{
let mut serializer = JsonSerializer::with_options(options.clone());
serialize_root(&mut serializer, peek)
.map_err(|e| std::io::Error::other(alloc::format!("{:?}", e)))?;
writer.write_all(&serializer.finish())
}
#[cfg(test)]
mod tests {
use facet::Facet;
use super::{BytesFormat, HexBytesOptions, SerializeOptions, to_string_with_options};
#[derive(Facet)]
struct BytesVec {
data: Vec<u8>,
}
#[derive(Facet)]
struct BytesArray {
data: [u8; 4],
}
#[test]
fn bytes_default_to_json_array() {
let value = BytesVec {
data: vec![0, 127, 255],
};
let json = to_string_with_options(&value, &SerializeOptions::default()).unwrap();
assert_eq!(json, r#"{"data":[0,127,255]}"#);
}
#[test]
fn bytes_can_serialize_as_hex_string() {
let value = BytesVec {
data: vec![0x00, 0x7f, 0xff],
};
let json =
to_string_with_options(&value, &SerializeOptions::default().bytes_as_hex()).unwrap();
assert_eq!(json, r#"{"data":"0x007fff"}"#);
}
#[test]
fn bytes_can_serialize_as_truncated_hex_string() {
let value = BytesVec {
data: (0u8..10).collect(),
};
let options = SerializeOptions::default()
.bytes_as_hex_with_options(HexBytesOptions::new().truncate(6).head_tail(2, 2));
let json = to_string_with_options(&value, &options).unwrap();
assert_eq!(json, r#"{"data":"0x0001..<6 bytes>..0809"}"#);
}
#[test]
fn byte_arrays_use_same_hex_mode() {
let value = BytesArray {
data: [0xaa, 0xbb, 0xcc, 0xdd],
};
let options =
SerializeOptions::default().bytes_format(BytesFormat::Hex(HexBytesOptions::new()));
let json = to_string_with_options(&value, &options).unwrap();
assert_eq!(json, r#"{"data":"0xaabbccdd"}"#);
}
}