use std::io::{self, Write};
use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputMode {
Pretty,
Compact,
Raw,
}
#[derive(Debug, Clone)]
pub struct ColorScheme {
pub null: &'static str,
pub bool_val: &'static str,
pub number: &'static str,
pub string: &'static str,
pub array_bracket: &'static str,
pub object_brace: &'static str,
pub object_key: &'static str,
pub reset: &'static str,
}
impl ColorScheme {
pub fn jq_default() -> Self {
Self {
null: "\x1b[0;90m",
bool_val: "\x1b[0;39m",
number: "\x1b[0;39m",
string: "\x1b[0;32m",
array_bracket: "\x1b[1;39m",
object_brace: "\x1b[1;39m",
object_key: "\x1b[1;34m",
reset: "\x1b[0m",
}
}
pub fn none() -> Self {
Self {
null: "",
bool_val: "",
number: "",
string: "",
array_bracket: "",
object_brace: "",
object_key: "",
reset: "",
}
}
fn is_enabled(&self) -> bool {
!self.reset.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct OutputConfig {
pub mode: OutputMode,
pub indent: String,
pub sort_keys: bool,
pub join_output: bool,
pub color: ColorScheme,
pub null_separator: bool,
pub ascii_output: bool,
pub unbuffered: bool,
pub seq: bool,
}
impl Default for OutputConfig {
fn default() -> Self {
Self {
mode: OutputMode::Pretty,
indent: " ".to_string(),
sort_keys: false,
join_output: false,
color: ColorScheme::none(),
null_separator: false,
ascii_output: false,
unbuffered: false,
seq: false,
}
}
}
pub fn format_compact(value: &Value) -> String {
let config = OutputConfig {
mode: OutputMode::Compact,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, value, &config).unwrap();
String::from_utf8(buf).unwrap().trim_end().to_string()
}
pub fn write_value<W: Write>(w: &mut W, value: &Value, config: &OutputConfig) -> io::Result<()> {
if config.seq {
w.write_all(b"\x1e")?;
}
match config.mode {
OutputMode::Pretty => {
let fmt = PrettyFmt {
indent: &config.indent,
};
write_value_inner(
w,
value,
&fmt,
0,
config.sort_keys,
&config.color,
config.ascii_output,
)?;
}
OutputMode::Compact => {
write_value_inner(
w,
value,
&CompactFmt,
0,
config.sort_keys,
&config.color,
config.ascii_output,
)?;
}
OutputMode::Raw => write_raw(
w,
value,
config.sort_keys,
&config.color,
config.ascii_output,
)?,
}
if !config.join_output {
if config.null_separator {
w.write_all(b"\0")?;
} else {
w.write_all(b"\n")?;
}
}
if config.unbuffered {
w.flush()?;
}
Ok(())
}
trait JsonFormatter {
fn after_open<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()>;
fn before_first<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()>;
fn after_sep<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()>;
fn before_close<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()>;
fn after_colon<W: Write>(&self, w: &mut W) -> io::Result<()>;
}
struct CompactFmt;
impl JsonFormatter for CompactFmt {
#[inline]
fn after_open<W: Write>(&self, _w: &mut W, _depth: usize) -> io::Result<()> {
Ok(())
}
#[inline]
fn before_first<W: Write>(&self, _w: &mut W, _depth: usize) -> io::Result<()> {
Ok(())
}
#[inline]
fn after_sep<W: Write>(&self, _w: &mut W, _depth: usize) -> io::Result<()> {
Ok(())
}
#[inline]
fn before_close<W: Write>(&self, _w: &mut W, _depth: usize) -> io::Result<()> {
Ok(())
}
#[inline]
fn after_colon<W: Write>(&self, _w: &mut W) -> io::Result<()> {
Ok(())
}
}
struct PrettyFmt<'a> {
indent: &'a str,
}
impl<'a> JsonFormatter for PrettyFmt<'a> {
fn after_open<W: Write>(&self, w: &mut W, _depth: usize) -> io::Result<()> {
w.write_all(b"\n")
}
fn before_first<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()> {
write_indent(w, depth + 1, self.indent)
}
fn after_sep<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()> {
w.write_all(b"\n")?;
write_indent(w, depth + 1, self.indent)
}
fn before_close<W: Write>(&self, w: &mut W, depth: usize) -> io::Result<()> {
w.write_all(b"\n")?;
write_indent(w, depth, self.indent)
}
fn after_colon<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_all(b" ")
}
}
#[inline]
fn write_colored<W: Write>(w: &mut W, ch: &[u8], color_code: &str, reset: &str) -> io::Result<()> {
if !color_code.is_empty() {
w.write_all(color_code.as_bytes())?;
w.write_all(ch)?;
w.write_all(reset.as_bytes())
} else {
w.write_all(ch)
}
}
fn write_value_inner<W: Write, F: JsonFormatter>(
w: &mut W,
value: &Value,
fmt: &F,
depth: usize,
sort_keys: bool,
color: &ColorScheme,
ascii_output: bool,
) -> io::Result<()> {
let c = color.is_enabled();
match value {
Value::Null => {
if c {
w.write_all(color.null.as_bytes())?;
}
w.write_all(b"null")?;
if c {
w.write_all(color.reset.as_bytes())?;
}
Ok(())
}
Value::Bool(b) => {
if c {
w.write_all(color.bool_val.as_bytes())?;
}
w.write_all(if *b { b"true" } else { b"false" })?;
if c {
w.write_all(color.reset.as_bytes())?;
}
Ok(())
}
Value::Int(n) => {
if c {
w.write_all(color.number.as_bytes())?;
}
let mut buf = itoa::Buffer::new();
w.write_all(buf.format(*n).as_bytes())?;
if c {
w.write_all(color.reset.as_bytes())?;
}
Ok(())
}
Value::Double(f, raw) => {
if c {
w.write_all(color.number.as_bytes())?;
}
write_double(w, *f, raw.as_deref())?;
if c {
w.write_all(color.reset.as_bytes())?;
}
Ok(())
}
Value::String(s) => {
if c {
w.write_all(color.string.as_bytes())?;
}
if ascii_output {
write_json_string_ascii(w, s)?;
} else {
write_json_string(w, s)?;
}
if c {
w.write_all(color.reset.as_bytes())?;
}
Ok(())
}
Value::Array(arr) if arr.is_empty() => {
write_colored(w, b"[]", color.array_bracket, color.reset)
}
Value::Array(arr) => {
write_colored(w, b"[", color.array_bracket, color.reset)?;
fmt.after_open(w, depth)?;
for (i, v) in arr.iter().enumerate() {
if i > 0 {
write_colored(w, b",", color.array_bracket, color.reset)?;
fmt.after_sep(w, depth)?;
} else {
fmt.before_first(w, depth)?;
}
write_value_inner(w, v, fmt, depth + 1, sort_keys, color, ascii_output)?;
}
fmt.before_close(w, depth)?;
write_colored(w, b"]", color.array_bracket, color.reset)
}
Value::Object(obj) if obj.is_empty() => {
write_colored(w, b"{}", color.object_brace, color.reset)
}
Value::Object(obj) => {
write_colored(w, b"{", color.object_brace, color.reset)?;
fmt.after_open(w, depth)?;
let sorted;
let pairs: &[(String, Value)] = if sort_keys {
sorted = {
let mut v = obj.as_ref().clone();
v.sort_by(|a, b| a.0.cmp(&b.0));
v
};
&sorted
} else {
obj.as_ref()
};
for (i, (k, v)) in pairs.iter().enumerate() {
if i > 0 {
write_colored(w, b",", color.object_brace, color.reset)?;
fmt.after_sep(w, depth)?;
} else {
fmt.before_first(w, depth)?;
}
if c {
w.write_all(color.object_key.as_bytes())?;
}
if ascii_output {
write_json_string_ascii(w, k)?;
} else {
write_json_string(w, k)?;
}
if c {
w.write_all(color.reset.as_bytes())?;
}
write_colored(w, b":", color.object_brace, color.reset)?;
fmt.after_colon(w)?;
write_value_inner(w, v, fmt, depth + 1, sort_keys, color, ascii_output)?;
}
fmt.before_close(w, depth)?;
write_colored(w, b"}", color.object_brace, color.reset)
}
}
}
pub(crate) fn write_compact<W: Write>(w: &mut W, value: &Value, sort_keys: bool) -> io::Result<()> {
write_value_inner(
w,
value,
&CompactFmt,
0,
sort_keys,
&ColorScheme::none(),
false,
)
}
fn write_raw<W: Write>(
w: &mut W,
value: &Value,
sort_keys: bool,
color: &ColorScheme,
ascii_output: bool,
) -> io::Result<()> {
match value {
Value::String(s) if !ascii_output => w.write_all(s.as_bytes()),
_ => write_value_inner(w, value, &CompactFmt, 0, sort_keys, color, ascii_output),
}
}
fn write_indent<W: Write>(w: &mut W, depth: usize, indent: &str) -> io::Result<()> {
for _ in 0..depth {
w.write_all(indent.as_bytes())?;
}
Ok(())
}
pub fn write_json_string<W: Write>(w: &mut W, s: &str) -> io::Result<()> {
w.write_all(b"\"")?;
let bytes = s.as_bytes();
let mut start = 0;
for (i, &b) in bytes.iter().enumerate() {
let escape: Option<&[u8]> = match b {
b'"' => Some(b"\\\""),
b'\\' => Some(b"\\\\"),
b'\n' => Some(b"\\n"),
b'\r' => Some(b"\\r"),
b'\t' => Some(b"\\t"),
b'\x08' => Some(b"\\b"),
b'\x0c' => Some(b"\\f"),
0..=0x1f => None, _ => continue,
};
if let Some(esc) = escape {
if start < i {
w.write_all(&bytes[start..i])?;
}
w.write_all(esc)?;
start = i + 1;
} else if b <= 0x1f {
if start < i {
w.write_all(&bytes[start..i])?;
}
write!(w, "\\u{:04x}", b)?;
start = i + 1;
}
}
if start < bytes.len() {
w.write_all(&bytes[start..])?;
}
w.write_all(b"\"")
}
fn write_json_string_ascii<W: Write>(w: &mut W, s: &str) -> io::Result<()> {
w.write_all(b"\"")?;
for ch in s.chars() {
match ch {
'"' => w.write_all(b"\\\"")?,
'\\' => w.write_all(b"\\\\")?,
'\n' => w.write_all(b"\\n")?,
'\r' => w.write_all(b"\\r")?,
'\t' => w.write_all(b"\\t")?,
'\x08' => w.write_all(b"\\b")?,
'\x0c' => w.write_all(b"\\f")?,
c if (c as u32) < 0x20 => write!(w, "\\u{:04x}", c as u32)?,
c if c.is_ascii() => w.write_all(&[c as u8])?,
c if (c as u32) <= 0xFFFF => write!(w, "\\u{:04x}", c as u32)?,
c => {
let n = c as u32 - 0x10000;
let hi = 0xD800 + (n >> 10);
let lo = 0xDC00 + (n & 0x3FF);
write!(w, "\\u{:04x}\\u{:04x}", hi, lo)?;
}
}
}
w.write_all(b"\"")
}
fn write_double<W: Write>(w: &mut W, f: f64, raw: Option<&str>) -> io::Result<()> {
if let Some(text) = raw {
return w.write_all(text.as_bytes());
}
if f.is_nan() {
return w.write_all(b"null");
}
if f.is_infinite() {
if f.is_sign_positive() {
return w.write_all(b"1.7976931348623157e+308");
} else {
return w.write_all(b"-1.7976931348623157e+308");
}
}
let mut buf = ryu::Buffer::new();
let s = buf.format(f);
if f.fract() == 0.0 {
if let Some(e_pos) = s.find('e') {
let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0);
if exp > 0 {
let mantissa = &s[..e_pos];
let frac_len = mantissa.find('.').map_or(0, |d| mantissa.len() - d - 1);
let zeros_needed = exp as usize - frac_len;
if zeros_needed <= 15 {
let (int_part, frac_part) = match mantissa.find('.') {
Some(d) => (&mantissa[..d], &mantissa[d + 1..]),
None => (mantissa, ""),
};
w.write_all(int_part.as_bytes())?;
w.write_all(frac_part.as_bytes())?;
for _ in 0..zeros_needed {
w.write_all(b"0")?;
}
return Ok(());
}
}
}
if let Some(stripped) = s.strip_suffix(".0") {
return w.write_all(stripped.as_bytes());
}
}
if let Some(e_pos) = s.find('e') {
w.write_all(&s.as_bytes()[..e_pos])?;
w.write_all(b"e")?;
let exp_str = &s[e_pos + 1..];
if !exp_str.starts_with('-') {
w.write_all(b"+")?;
}
return w.write_all(exp_str.as_bytes());
}
w.write_all(s.as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn compact(v: &Value) -> String {
let config = OutputConfig {
mode: OutputMode::Compact,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, v, &config).unwrap();
String::from_utf8(buf).unwrap().trim_end().to_string()
}
fn pretty(v: &Value) -> String {
let config = OutputConfig {
mode: OutputMode::Pretty,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, v, &config).unwrap();
String::from_utf8(buf).unwrap().trim_end().to_string()
}
fn raw(v: &Value) -> String {
let config = OutputConfig {
mode: OutputMode::Raw,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, v, &config).unwrap();
String::from_utf8(buf).unwrap().trim_end().to_string()
}
#[test]
fn compact_null() {
assert_eq!(compact(&Value::Null), "null");
}
#[test]
fn compact_bool() {
assert_eq!(compact(&Value::Bool(true)), "true");
assert_eq!(compact(&Value::Bool(false)), "false");
}
#[test]
fn compact_int() {
assert_eq!(compact(&Value::Int(42)), "42");
assert_eq!(compact(&Value::Int(-1)), "-1");
assert_eq!(compact(&Value::Int(0)), "0");
}
#[test]
fn compact_double() {
assert_eq!(compact(&Value::Double(3.14, None)), "3.14");
assert_eq!(compact(&Value::Double(1.0, None)), "1");
assert_eq!(compact(&Value::Double(-0.0, None)), "-0");
}
#[test]
fn compact_double_raw_preserved() {
assert_eq!(compact(&Value::Double(75.8, Some("75.80".into()))), "75.80");
assert_eq!(
compact(&Value::Double(150.0, Some("1.5e2".into()))),
"1.5e2"
);
}
#[test]
fn compact_string() {
assert_eq!(compact(&Value::String("hello".into())), r#""hello""#);
}
#[test]
fn compact_string_escaping() {
assert_eq!(
compact(&Value::String("a\"b\\c\nd".into())),
r#""a\"b\\c\nd""#
);
}
#[test]
fn compact_control_chars() {
assert_eq!(
compact(&Value::String("\x00\x1f".into())),
r#""\u0000\u001f""#
);
}
#[test]
fn compact_array() {
let v = Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
assert_eq!(compact(&v), "[1,2,3]");
}
#[test]
fn compact_empty_array() {
assert_eq!(compact(&Value::Array(Arc::new(vec![]))), "[]");
}
#[test]
fn compact_object() {
let v = Value::Object(Arc::new(vec![
("a".into(), Value::Int(1)),
("b".into(), Value::Bool(true)),
]));
assert_eq!(compact(&v), r#"{"a":1,"b":true}"#);
}
#[test]
fn compact_empty_object() {
assert_eq!(compact(&Value::Object(Arc::new(vec![]))), "{}");
}
#[test]
fn pretty_object() {
let v = Value::Object(Arc::new(vec![
("a".into(), Value::Int(1)),
("b".into(), Value::Int(2)),
]));
assert_eq!(pretty(&v), "{\n \"a\": 1,\n \"b\": 2\n}");
}
#[test]
fn pretty_nested() {
let v = Value::Object(Arc::new(vec![(
"arr".into(),
Value::Array(Arc::new(vec![Value::Int(1), Value::Int(2)])),
)]));
assert_eq!(pretty(&v), "{\n \"arr\": [\n 1,\n 2\n ]\n}");
}
#[test]
fn raw_string() {
assert_eq!(raw(&Value::String("hello world".into())), "hello world");
}
#[test]
fn raw_non_string() {
assert_eq!(raw(&Value::Int(42)), "42");
}
#[test]
fn double_nan() {
assert_eq!(compact(&Value::Double(f64::NAN, None)), "null");
}
#[test]
fn double_infinity() {
assert_eq!(
compact(&Value::Double(f64::INFINITY, None)),
"1.7976931348623157e+308"
);
assert_eq!(
compact(&Value::Double(f64::NEG_INFINITY, None)),
"-1.7976931348623157e+308"
);
}
#[test]
fn large_int() {
assert_eq!(
compact(&Value::Int(9223372036854775807)),
"9223372036854775807"
);
}
#[test]
fn double_at_i64_max_boundary() {
let val = Value::Double(9223372036854775808.0, None);
let s = compact(&val);
assert_ne!(s, "9223372036854775807");
assert_eq!(s, "9223372036854776000");
}
#[test]
fn double_large_integer_plain_format() {
assert_eq!(
compact(&Value::Double(5.564623688220226e21, None)),
"5564623688220226000000" );
assert_eq!(compact(&Value::Double(1e20, None)), "1e+20"); assert_eq!(compact(&Value::Double(-1e19, None)), "-1e+19"); assert_eq!(compact(&Value::Double(1e15, None)), "1000000000000000");
assert_eq!(compact(&Value::Double(1e16, None)), "1e+16");
}
#[test]
fn double_at_i64_min_formats_via_ryu() {
let val = Value::Double(i64::MIN as f64, None);
assert_eq!(compact(&val), "-9223372036854776000");
}
#[test]
fn sort_keys_object() {
let v = Value::Object(Arc::new(vec![
("b".into(), Value::Int(2)),
("a".into(), Value::Int(1)),
]));
let config = OutputConfig {
mode: OutputMode::Compact,
sort_keys: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
assert_eq!(String::from_utf8(buf).unwrap().trim(), r#"{"a":1,"b":2}"#);
}
#[test]
fn sort_keys_nested() {
let v = Value::Object(Arc::new(vec![
(
"z".into(),
Value::Object(Arc::new(vec![
("b".into(), Value::Int(2)),
("a".into(), Value::Int(1)),
])),
),
("a".into(), Value::Int(0)),
]));
let config = OutputConfig {
mode: OutputMode::Compact,
sort_keys: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
assert_eq!(
String::from_utf8(buf).unwrap().trim(),
r#"{"a":0,"z":{"a":1,"b":2}}"#
);
}
#[test]
fn join_output_no_newline() {
let v = Value::String("hello".into());
let config = OutputConfig {
mode: OutputMode::Raw,
join_output: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
assert_eq!(buf, b"hello"); }
#[test]
fn raw_output_control_characters() {
let config = OutputConfig {
mode: OutputMode::Raw,
..Default::default()
};
let v = Value::String("a\tb\nc\rd".into());
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
assert_eq!(buf, b"a\tb\nc\rd\n");
let v = Value::String("before\0after".into());
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
assert_eq!(buf, b"before\0after\n");
}
#[test]
fn compact_control_characters_escaped() {
let v = Value::String("a\tb\nc".into());
let s = compact(&v);
assert_eq!(s, r#""a\tb\nc""#);
let v = Value::String("x\0y".into());
let s = compact(&v);
assert_eq!(s, r#""x\u0000y""#);
let v = Value::String("x\x07y".into());
let s = compact(&v);
assert_eq!(s, r#""x\u0007y""#);
}
#[test]
fn pretty_deeply_nested_array() {
let mut v = Value::Int(42);
for _ in 0..50 {
v = Value::Array(Arc::new(vec![v]));
}
let config = OutputConfig {
mode: OutputMode::Pretty,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
let s = String::from_utf8(buf).unwrap();
assert_eq!(s.matches('[').count(), 50);
assert_eq!(s.matches(']').count(), 50);
assert!(s.contains("42"));
let deepest_line = s.lines().find(|l| l.contains("42")).unwrap();
let indent = deepest_line.len() - deepest_line.trim_start().len();
assert_eq!(indent, 100); }
#[test]
fn pretty_deeply_nested_object() {
let mut v = Value::Int(1);
for _ in 0..30 {
v = Value::Object(Arc::new(vec![("a".into(), v)]));
}
let config = OutputConfig {
mode: OutputMode::Pretty,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
let s = String::from_utf8(buf).unwrap();
assert_eq!(s.matches('{').count(), 30);
assert_eq!(s.matches('}').count(), 30);
assert!(s.contains(": 1"));
}
#[test]
fn ascii_output_surrogate_pairs() {
let v = Value::String("🎉".into()); let config = OutputConfig {
mode: OutputMode::Compact,
ascii_output: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
let s = String::from_utf8(buf).unwrap().trim_end().to_string();
assert_eq!(s, r#""\ud83c\udf89""#);
}
#[test]
fn ascii_output_bmp_characters() {
let v = Value::String("café".into());
let config = OutputConfig {
mode: OutputMode::Compact,
ascii_output: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
let s = String::from_utf8(buf).unwrap().trim_end().to_string();
assert_eq!(s, r#""caf\u00e9""#);
}
#[test]
fn ascii_output_control_chars() {
let v = Value::String("\x01\x02\x1f".into());
let config = OutputConfig {
mode: OutputMode::Compact,
ascii_output: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
let s = String::from_utf8(buf).unwrap().trim_end().to_string();
assert_eq!(s, r#""\u0001\u0002\u001f""#);
}
#[test]
fn compact_large_object() {
let pairs: Vec<(String, Value)> =
(0..100).map(|i| (format!("k{i}"), Value::Int(i))).collect();
let v = Value::Object(Arc::new(pairs));
let s = compact(&v);
assert!(s.starts_with('{'));
assert!(s.ends_with('}'));
for i in 0..100 {
assert!(s.contains(&format!(r#""k{i}""#)));
}
assert!(!s.contains('\n'));
}
#[test]
fn sort_keys_deeply_nested() {
let inner = Value::Object(Arc::new(vec![
("z".into(), Value::Int(1)),
("a".into(), Value::Int(2)),
]));
let arr = Value::Array(Arc::new(vec![
Value::Object(Arc::new(vec![
("m".into(), Value::Int(3)),
("b".into(), Value::Int(4)),
])),
inner,
]));
let outer = Value::Object(Arc::new(vec![
("y".into(), arr),
("x".into(), Value::Int(0)),
]));
let config = OutputConfig {
mode: OutputMode::Compact,
sort_keys: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &outer, &config).unwrap();
let s = String::from_utf8(buf).unwrap().trim_end().to_string();
assert_eq!(s, r#"{"x":0,"y":[{"b":4,"m":3},{"a":2,"z":1}]}"#);
}
#[test]
fn double_neg_infinity() {
assert_eq!(
compact(&Value::Double(f64::NEG_INFINITY, None)),
"-1.7976931348623157e+308"
);
}
#[test]
fn double_negative_zero() {
let s = compact(&Value::Double(-0.0, None));
assert_eq!(s, "-0");
}
#[test]
fn raw_output0_nul_separator() {
let v = Value::String("hello".into());
let config = OutputConfig {
mode: OutputMode::Raw,
null_separator: true,
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
assert_eq!(buf, b"hello\0");
}
#[test]
fn pretty_tab_indent() {
let v = Value::Object(Arc::new(vec![("a".into(), Value::Int(1))]));
let config = OutputConfig {
mode: OutputMode::Pretty,
indent: "\t".into(),
..Default::default()
};
let mut buf = Vec::new();
write_value(&mut buf, &v, &config).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("\t\"a\""));
}
}