use simd_json::OwnedValue as Value;
use simd_json::StaticNode;
const RESET: &str = "\x1b[0m";
const NULL: &str = "\x1b[0;90m"; const STRING: &str = "\x1b[0;32m"; const KEY: &str = "\x1b[1;34m";
pub fn to_string_pretty(value: &Value) -> String {
let mut output = String::new();
write_value(value, 0, false, &mut output);
output
}
pub fn to_string_pretty_colored(value: &Value) -> String {
let mut output = String::new();
write_value(value, 0, true, &mut output);
output
}
fn write_value(value: &Value, indent: usize, color: bool, out: &mut String) {
match value {
Value::Static(StaticNode::Null) => {
if color {
out.push_str(NULL);
}
out.push_str("null");
if color {
out.push_str(RESET);
}
}
Value::Static(StaticNode::Bool(b)) => {
out.push_str(if *b { "true" } else { "false" });
}
Value::Static(StaticNode::I64(n)) => {
out.push_str(&n.to_string());
}
Value::Static(StaticNode::U64(n)) => {
out.push_str(&n.to_string());
}
Value::Static(StaticNode::F64(n)) => {
if n.fract() == 0.0 && n.abs() < 1e15 {
out.push_str(&format!("{:.0}", n));
} else {
out.push_str(&n.to_string());
}
}
Value::String(s) => {
if color {
out.push_str(STRING);
}
out.push('"');
escape_string(s, out);
out.push('"');
if color {
out.push_str(RESET);
}
}
Value::Array(arr) => {
if arr.is_empty() {
out.push_str("[]");
} else {
out.push_str("[\n");
for (i, item) in arr.iter().enumerate() {
write_indent(indent + 1, out);
write_value(item, indent + 1, color, out);
if i < arr.len() - 1 {
out.push(',');
}
out.push('\n');
}
write_indent(indent, out);
out.push(']');
}
}
Value::Object(obj) => {
if obj.is_empty() {
out.push_str("{}");
} else {
out.push_str("{\n");
let len = obj.len();
for (i, (key, val)) in obj.iter().enumerate() {
write_indent(indent + 1, out);
if color {
out.push_str(KEY);
}
out.push('"');
escape_string(key, out);
out.push('"');
if color {
out.push_str(RESET);
}
out.push_str(": ");
write_value(val, indent + 1, color, out);
if i < len - 1 {
out.push(',');
}
out.push('\n');
}
write_indent(indent, out);
out.push('}');
}
}
}
}
fn write_indent(level: usize, out: &mut String) {
for _ in 0..level {
out.push_str(" ");
}
}
fn escape_string(s: &str, out: &mut String) {
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c.is_control() => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use simd_json::json;
#[test]
fn test_primitives() {
assert_eq!(to_string_pretty(&json!(null)), "null");
assert_eq!(to_string_pretty(&json!(true)), "true");
assert_eq!(to_string_pretty(&json!(false)), "false");
assert_eq!(to_string_pretty(&json!(42)), "42");
assert_eq!(to_string_pretty(&json!(-17)), "-17");
assert_eq!(to_string_pretty(&json!("hello")), "\"hello\"");
}
#[test]
fn test_string_escaping() {
assert_eq!(to_string_pretty(&json!("a\"b")), "\"a\\\"b\"");
assert_eq!(to_string_pretty(&json!("a\\b")), "\"a\\\\b\"");
assert_eq!(to_string_pretty(&json!("a\nb")), "\"a\\nb\"");
}
#[test]
fn test_empty_containers() {
assert_eq!(to_string_pretty(&json!([])), "[]");
assert_eq!(to_string_pretty(&json!({})), "{}");
}
#[test]
fn test_array() {
let expected = "[\n 1,\n 2,\n 3\n]";
assert_eq!(to_string_pretty(&json!([1, 2, 3])), expected);
}
#[test]
fn test_object() {
let expected = "{\n \"a\": 1\n}";
assert_eq!(to_string_pretty(&json!({"a": 1})), expected);
}
#[test]
fn test_nested() {
let expected = "{\n \"arr\": [\n 1,\n 2\n ]\n}";
assert_eq!(to_string_pretty(&json!({"arr": [1, 2]})), expected);
}
}