use core::fmt;
use crate::number::NumberValue;
use crate::owned::OwnedDataValue;
use crate::value::DataValue;
pub(crate) trait JsonSink {
type Error;
fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error>;
fn write_byte(&mut self, b: u8) -> Result<(), Self::Error>;
}
impl JsonSink for Vec<u8> {
type Error = core::convert::Infallible;
#[inline]
fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
self.extend_from_slice(b);
Ok(())
}
#[inline]
fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
self.push(b);
Ok(())
}
}
impl JsonSink for bumpalo::collections::Vec<'_, u8> {
type Error = core::convert::Infallible;
#[inline]
fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
self.reserve(b.len());
let len = self.len();
unsafe {
core::ptr::copy_nonoverlapping(b.as_ptr(), self.as_mut_ptr().add(len), b.len());
self.set_len(len + b.len());
}
Ok(())
}
#[inline]
fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
self.push(b);
Ok(())
}
}
#[inline]
fn into_arena_str<'b>(mut out: bumpalo::collections::Vec<'b, u8>) -> &'b str {
out.shrink_to_fit();
unsafe { core::str::from_utf8_unchecked(out.into_bump_slice()) }
}
const FMT_STAGING: usize = 128;
struct FormatterSink<'a, 'b> {
f: &'a mut fmt::Formatter<'b>,
buf: [u8; FMT_STAGING],
len: usize,
}
impl<'a, 'b> FormatterSink<'a, 'b> {
fn new(f: &'a mut fmt::Formatter<'b>) -> Self {
FormatterSink {
f,
buf: [0; FMT_STAGING],
len: 0,
}
}
fn flush(&mut self) -> fmt::Result {
if self.len > 0 {
let s = unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) };
self.f.write_str(s)?;
self.len = 0;
}
Ok(())
}
}
impl<'a, 'b> JsonSink for FormatterSink<'a, 'b> {
type Error = fmt::Error;
#[inline]
fn write_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
if self.len + b.len() > FMT_STAGING {
self.flush()?;
if b.len() >= FMT_STAGING {
let s = unsafe { core::str::from_utf8_unchecked(b) };
return self.f.write_str(s);
}
}
self.buf[self.len..self.len + b.len()].copy_from_slice(b);
self.len += b.len();
Ok(())
}
#[inline]
fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
debug_assert!(b.is_ascii());
if self.len == FMT_STAGING {
self.flush()?;
}
self.buf[self.len] = b;
self.len += 1;
Ok(())
}
}
#[inline]
fn write_escaped_str<S: JsonSink>(out: &mut S, s: &str) -> Result<(), S::Error> {
out.write_byte(b'"')?;
let bytes = s.as_bytes();
let mut run_start = 0;
while let Some(off) = crate::simd::find_string_terminator(&bytes[run_start..]) {
let hit = run_start + off;
if hit > run_start {
out.write_bytes(&bytes[run_start..hit])?;
}
write_escape_byte(out, bytes[hit])?;
run_start = hit + 1;
}
if run_start < bytes.len() {
out.write_bytes(&bytes[run_start..])?;
}
out.write_byte(b'"')
}
#[cfg(feature = "datetime")]
fn write_datetime<S: JsonSink>(
out: &mut S,
d: &crate::datetime::DataDateTime,
) -> Result<(), S::Error> {
match d.iso_secs_buf() {
Some(buf) => {
out.write_byte(b'"')?;
out.write_bytes(&buf)?;
out.write_byte(b'"')
}
None => write_escaped_str(out, &d.to_iso_string()),
}
}
#[cfg(feature = "datetime")]
fn write_duration<S: JsonSink>(
out: &mut S,
d: &crate::datetime::DataDuration,
) -> Result<(), S::Error> {
let (days, hours, minutes, seconds) = d.dhms();
let mut b = itoa::Buffer::new();
out.write_byte(b'"')?;
out.write_bytes(b.format(days).as_bytes())?;
out.write_bytes(b"d:")?;
out.write_bytes(b.format(hours).as_bytes())?;
out.write_bytes(b"h:")?;
out.write_bytes(b.format(minutes).as_bytes())?;
out.write_bytes(b"m:")?;
out.write_bytes(b.format(seconds).as_bytes())?;
out.write_bytes(b"s\"")
}
#[inline]
fn write_escape_byte<S: JsonSink>(out: &mut S, b: u8) -> Result<(), S::Error> {
match b {
b'"' => out.write_bytes(b"\\\""),
b'\\' => out.write_bytes(b"\\\\"),
b'\n' => out.write_bytes(b"\\n"),
b'\r' => out.write_bytes(b"\\r"),
b'\t' => out.write_bytes(b"\\t"),
0x08 => out.write_bytes(b"\\b"),
0x0C => out.write_bytes(b"\\f"),
c => {
const HEX: &[u8; 16] = b"0123456789abcdef";
out.write_bytes(b"\\u00")?;
out.write_byte(HEX[((c >> 4) & 0x0F) as usize])?;
out.write_byte(HEX[(c & 0x0F) as usize])
}
}
}
#[inline]
fn write_number<S: JsonSink>(out: &mut S, n: NumberValue) -> Result<(), S::Error> {
match n {
NumberValue::Integer(i) => {
let mut buf = itoa::Buffer::new();
out.write_bytes(buf.format(i).as_bytes())
}
NumberValue::Float(f) => {
if !f.is_finite() {
return out.write_bytes(b"null");
}
let mut buf = ryu::Buffer::new();
out.write_bytes(buf.format_finite(f).as_bytes())
}
}
}
fn write_data_value<S: JsonSink>(out: &mut S, v: &DataValue<'_>) -> Result<(), S::Error> {
match *v {
DataValue::Null => out.write_bytes(b"null"),
DataValue::Bool(true) => out.write_bytes(b"true"),
DataValue::Bool(false) => out.write_bytes(b"false"),
DataValue::Number(n) => write_number(out, n),
DataValue::String(s) => write_escaped_str(out, s),
DataValue::Array(items) => {
out.write_byte(b'[')?;
let mut first = true;
for item in items {
if !first {
out.write_byte(b',')?;
}
first = false;
write_data_value(out, item)?;
}
out.write_byte(b']')
}
DataValue::Object(pairs) => {
out.write_byte(b'{')?;
let mut first = true;
for (k, v) in pairs {
if !first {
out.write_byte(b',')?;
}
first = false;
write_escaped_str(out, k)?;
out.write_byte(b':')?;
write_data_value(out, v)?;
}
out.write_byte(b'}')
}
#[cfg(feature = "datetime")]
DataValue::DateTime(d) => write_datetime(out, &d),
#[cfg(feature = "datetime")]
DataValue::Duration(d) => write_duration(out, &d),
}
}
fn write_owned_value<S: JsonSink>(out: &mut S, v: &OwnedDataValue) -> Result<(), S::Error> {
match v {
OwnedDataValue::Null => out.write_bytes(b"null"),
OwnedDataValue::Bool(true) => out.write_bytes(b"true"),
OwnedDataValue::Bool(false) => out.write_bytes(b"false"),
OwnedDataValue::Number(n) => write_number(out, *n),
OwnedDataValue::String(s) => write_escaped_str(out, s),
OwnedDataValue::Array(items) => {
out.write_byte(b'[')?;
let mut first = true;
for item in items {
if !first {
out.write_byte(b',')?;
}
first = false;
write_owned_value(out, item)?;
}
out.write_byte(b']')
}
OwnedDataValue::Object(pairs) => {
out.write_byte(b'{')?;
let mut first = true;
for (k, v) in pairs {
if !first {
out.write_byte(b',')?;
}
first = false;
write_escaped_str(out, k)?;
out.write_byte(b':')?;
write_owned_value(out, v)?;
}
out.write_byte(b'}')
}
#[cfg(feature = "datetime")]
OwnedDataValue::DateTime(d) => write_datetime(out, d),
#[cfg(feature = "datetime")]
OwnedDataValue::Duration(d) => write_duration(out, d),
}
}
#[inline]
fn write_indent<S: JsonSink>(out: &mut S, depth: usize) -> Result<(), S::Error> {
const SPACES: &[u8; 64] = b" ";
let mut remaining = depth * 2;
while remaining > 0 {
let chunk = remaining.min(SPACES.len());
out.write_bytes(&SPACES[..chunk])?;
remaining -= chunk;
}
Ok(())
}
fn write_data_value_pretty<S: JsonSink>(
out: &mut S,
v: &DataValue<'_>,
depth: usize,
) -> Result<(), S::Error> {
match *v {
DataValue::Null => out.write_bytes(b"null"),
DataValue::Bool(true) => out.write_bytes(b"true"),
DataValue::Bool(false) => out.write_bytes(b"false"),
DataValue::Number(n) => write_number(out, n),
DataValue::String(s) => write_escaped_str(out, s),
DataValue::Array(items) => {
if items.is_empty() {
return out.write_bytes(b"[]");
}
out.write_byte(b'[')?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.write_byte(b',')?;
}
out.write_byte(b'\n')?;
write_indent(out, depth + 1)?;
write_data_value_pretty(out, item, depth + 1)?;
}
out.write_byte(b'\n')?;
write_indent(out, depth)?;
out.write_byte(b']')
}
DataValue::Object(pairs) => {
if pairs.is_empty() {
return out.write_bytes(b"{}");
}
out.write_byte(b'{')?;
for (i, (k, v)) in pairs.iter().enumerate() {
if i > 0 {
out.write_byte(b',')?;
}
out.write_byte(b'\n')?;
write_indent(out, depth + 1)?;
write_escaped_str(out, k)?;
out.write_bytes(b": ")?;
write_data_value_pretty(out, v, depth + 1)?;
}
out.write_byte(b'\n')?;
write_indent(out, depth)?;
out.write_byte(b'}')
}
#[cfg(feature = "datetime")]
DataValue::DateTime(d) => write_datetime(out, &d),
#[cfg(feature = "datetime")]
DataValue::Duration(d) => write_duration(out, &d),
}
}
fn write_owned_value_pretty<S: JsonSink>(
out: &mut S,
v: &OwnedDataValue,
depth: usize,
) -> Result<(), S::Error> {
match v {
OwnedDataValue::Null => out.write_bytes(b"null"),
OwnedDataValue::Bool(true) => out.write_bytes(b"true"),
OwnedDataValue::Bool(false) => out.write_bytes(b"false"),
OwnedDataValue::Number(n) => write_number(out, *n),
OwnedDataValue::String(s) => write_escaped_str(out, s),
OwnedDataValue::Array(items) => {
if items.is_empty() {
return out.write_bytes(b"[]");
}
out.write_byte(b'[')?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.write_byte(b',')?;
}
out.write_byte(b'\n')?;
write_indent(out, depth + 1)?;
write_owned_value_pretty(out, item, depth + 1)?;
}
out.write_byte(b'\n')?;
write_indent(out, depth)?;
out.write_byte(b']')
}
OwnedDataValue::Object(pairs) => {
if pairs.is_empty() {
return out.write_bytes(b"{}");
}
out.write_byte(b'{')?;
for (i, (k, v)) in pairs.iter().enumerate() {
if i > 0 {
out.write_byte(b',')?;
}
out.write_byte(b'\n')?;
write_indent(out, depth + 1)?;
write_escaped_str(out, k)?;
out.write_bytes(b": ")?;
write_owned_value_pretty(out, v, depth + 1)?;
}
out.write_byte(b'\n')?;
write_indent(out, depth)?;
out.write_byte(b'}')
}
#[cfg(feature = "datetime")]
OwnedDataValue::DateTime(d) => write_datetime(out, d),
#[cfg(feature = "datetime")]
OwnedDataValue::Duration(d) => write_duration(out, d),
}
}
impl DataValue<'_> {
pub fn write_json_into(&self, out: &mut Vec<u8>) {
let _ = write_data_value(out, self);
}
pub fn pretty(&self) -> Pretty<'_, DataValue<'_>> {
Pretty(self)
}
pub fn write_json_pretty_into(&self, out: &mut Vec<u8>) {
let _ = write_data_value_pretty(out, self, 0);
}
pub fn to_json_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
let mut out = bumpalo::collections::Vec::new_in(arena);
let _ = write_data_value(&mut out, self);
into_arena_str(out)
}
pub fn to_json_pretty_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
let mut out = bumpalo::collections::Vec::new_in(arena);
let _ = write_data_value_pretty(&mut out, self, 0);
into_arena_str(out)
}
}
impl OwnedDataValue {
pub fn write_json_into(&self, out: &mut Vec<u8>) {
let _ = write_owned_value(out, self);
}
pub fn pretty(&self) -> Pretty<'_, OwnedDataValue> {
Pretty(self)
}
pub fn write_json_pretty_into(&self, out: &mut Vec<u8>) {
let _ = write_owned_value_pretty(out, self, 0);
}
pub fn to_json_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
let mut out = bumpalo::collections::Vec::new_in(arena);
let _ = write_owned_value(&mut out, self);
into_arena_str(out)
}
pub fn to_json_pretty_str_in<'b>(&self, arena: &'b bumpalo::Bump) -> &'b str {
let mut out = bumpalo::collections::Vec::new_in(arena);
let _ = write_owned_value_pretty(&mut out, self, 0);
into_arena_str(out)
}
}
pub struct Pretty<'b, T: ?Sized>(&'b T);
impl fmt::Display for DataValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sink = FormatterSink::new(f);
write_data_value(&mut sink, self)?;
sink.flush()
}
}
impl fmt::Display for OwnedDataValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sink = FormatterSink::new(f);
write_owned_value(&mut sink, self)?;
sink.flush()
}
}
impl fmt::Display for Pretty<'_, DataValue<'_>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sink = FormatterSink::new(f);
write_data_value_pretty(&mut sink, self.0, 0)?;
sink.flush()
}
}
impl fmt::Display for Pretty<'_, OwnedDataValue> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sink = FormatterSink::new(f);
write_owned_value_pretty(&mut sink, self.0, 0)?;
sink.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bumpalo::Bump;
fn round_trip(s: &str) -> String {
let arena = Bump::new();
let v = DataValue::from_str(s, &arena).unwrap();
v.to_string()
}
#[test]
fn primitives() {
assert_eq!(round_trip("null"), "null");
assert_eq!(round_trip("true"), "true");
assert_eq!(round_trip("false"), "false");
assert_eq!(round_trip("42"), "42");
assert_eq!(round_trip("-7"), "-7");
assert_eq!(round_trip("3.5"), "3.5");
}
#[test]
fn strings_with_escapes() {
assert_eq!(round_trip(r#""hello""#), r#""hello""#);
assert_eq!(round_trip(r#""a\nb""#), r#""a\nb""#);
assert_eq!(round_trip(r#""a\\b""#), r#""a\\b""#);
assert_eq!(round_trip(r#""a\"b""#), r#""a\"b""#);
assert_eq!(round_trip(r#""café""#), r#""café""#);
}
#[test]
fn control_bytes_render_as_unicode_escapes() {
let arena = Bump::new();
let v = DataValue::from_str("\"\\u0001\"", &arena).unwrap();
assert_eq!(v.to_string(), "\"\\u0001\"");
}
#[test]
fn nested_round_trip_matches_serde_json() {
let input = r#"{"a":[1,2,{"b":"hi\n","c":null,"d":true}],"e":-3.5,"f":[],"g":{}}"#;
let arena = Bump::new();
let v = DataValue::from_str(input, &arena).unwrap();
let ours = v.to_string();
let serde: serde_json::Value = serde_json::from_str(input).unwrap();
let theirs = serde_json::to_string(&serde).unwrap();
assert_eq!(ours, theirs);
}
#[test]
fn long_string_swar_path() {
let arena = Bump::new();
let s = format!("\"{}\"", "x".repeat(200));
let v = DataValue::from_str(&s, &arena).unwrap();
assert_eq!(v.to_string(), s);
}
fn assert_display_matches_vec(input: &str) {
let arena = Bump::new();
let v = DataValue::from_str(input, &arena).unwrap();
let mut buf = Vec::new();
v.write_json_into(&mut buf);
assert_eq!(v.to_string().into_bytes(), buf, "compact mismatch");
let mut pretty_buf = Vec::new();
v.write_json_pretty_into(&mut pretty_buf);
assert_eq!(
v.pretty().to_string().into_bytes(),
pretty_buf,
"pretty mismatch"
);
}
#[cfg(feature = "datetime")]
#[test]
fn datetime_emit_matches_display_wire_format() {
use crate::datetime::DataDateTime;
let dt = DataDateTime::parse("2024-01-15T12:30:45Z").unwrap();
let later = DataDateTime::parse("2024-01-18T16:35:51Z").unwrap();
let du = later.diff(&dt);
for v in [DataValue::DateTime(dt), DataValue::Duration(du)] {
let display = v.to_string();
let mut buf = Vec::new();
v.write_json_into(&mut buf);
assert_eq!(display.into_bytes(), buf);
let owned = v.to_owned();
assert_eq!(owned.to_string(), v.to_string());
}
assert_eq!(
DataValue::DateTime(dt).to_string(),
"\"2024-01-15T12:30:45Z\""
);
assert_eq!(DataValue::Duration(du).to_string(), "\"3d:4h:5m:6s\"");
}
#[test]
fn to_json_str_in_matches_to_string() {
let inputs = [
"null",
"[]",
r#"{"a":[1,2.5,"hi\n",null,true],"b":{"c":"é€"},"e":-0.125}"#,
];
let arena = Bump::new();
let out_arena = Bump::new();
for input in inputs {
let v = DataValue::from_str(input, &arena).unwrap();
assert_eq!(v.to_json_str_in(&out_arena), v.to_string());
assert_eq!(v.to_json_pretty_str_in(&out_arena), v.pretty().to_string());
let owned = v.to_owned();
assert_eq!(owned.to_json_str_in(&out_arena), owned.to_string());
assert_eq!(
owned.to_json_pretty_str_in(&out_arena),
owned.pretty().to_string()
);
}
let long = format!("\"{}\"", "x".repeat(5000));
let v = DataValue::from_str(&long, &arena).unwrap();
assert_eq!(v.to_json_str_in(&out_arena), long);
}
#[test]
fn display_matches_vec_across_staging_boundaries() {
for n in [1, 7, 126, 127, 128, 129, 200, 255, 256, 257, 1000] {
assert_display_matches_vec(&format!("\"{}\"", "x".repeat(n)));
}
for n in [60, 63, 64, 65, 100] {
assert_display_matches_vec(&format!("\"{}\"", "é".repeat(n)));
assert_display_matches_vec(&format!("\"{}\"", "€".repeat(n)));
}
assert_display_matches_vec(&format!("\"{}\"", r#"a\n"#.repeat(100)));
assert_display_matches_vec(
r#"{"a":[1,2.5,"hi\n",null,true],"b":{"c":"é€","d":[[],{}]},"e":-0.125}"#,
);
}
#[test]
fn non_finite_floats_render_as_null() {
let v = DataValue::from_f64(f64::NAN);
assert_eq!(v.to_string(), "null");
let v = DataValue::from_f64(f64::INFINITY);
assert_eq!(v.to_string(), "null");
}
#[test]
fn owned_round_trip() {
let v: OwnedDataValue = r#"{"name":"alice","age":30}"#.parse().unwrap();
let serde: serde_json::Value = serde_json::from_str(&v.to_string()).unwrap();
assert_eq!(serde["name"], "alice");
assert_eq!(serde["age"], 30);
}
#[test]
fn write_json_into_buffer() {
let arena = Bump::new();
let v = DataValue::from_str(r#"[1,2,3]"#, &arena).unwrap();
let mut buf = Vec::new();
v.write_json_into(&mut buf);
assert_eq!(buf, b"[1,2,3]");
}
#[test]
fn pretty_matches_serde_json_pretty() {
let input = r#"{"a":[1,2,{"b":"hi","c":null}],"e":-3.5,"f":[],"g":{}}"#;
let arena = Bump::new();
let v = DataValue::from_str(input, &arena).unwrap();
let ours = v.pretty().to_string();
let serde: serde_json::Value = serde_json::from_str(input).unwrap();
let theirs = serde_json::to_string_pretty(&serde).unwrap();
assert_eq!(ours, theirs);
}
#[test]
fn pretty_owned_matches_serde_json_pretty() {
let input = r#"{"a":[1,2,{"b":"hi","c":null}],"e":-3.5,"f":[],"g":{}}"#;
let v: OwnedDataValue = input.parse().unwrap();
let serde: serde_json::Value = serde_json::from_str(input).unwrap();
assert_eq!(
v.pretty().to_string(),
serde_json::to_string_pretty(&serde).unwrap()
);
}
#[test]
fn pretty_empty_collections_inline() {
let arena = Bump::new();
let v = DataValue::from_str(r#"{"a":[],"b":{}}"#, &arena).unwrap();
assert_eq!(v.pretty().to_string(), "{\n \"a\": [],\n \"b\": {}\n}");
}
#[test]
fn pretty_deep_indent_beyond_64_spaces() {
let arena = Bump::new();
let mut s = String::new();
for _ in 0..35 {
s.push('[');
}
s.push('1');
for _ in 0..35 {
s.push(']');
}
let v = DataValue::from_str(&s, &arena).unwrap();
let ours = v.pretty().to_string();
let serde: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(ours, serde_json::to_string_pretty(&serde).unwrap());
}
}