// 956: JSON Pretty Print
// Recursive pretty-printer: OCaml uses Buffer, Rust builds String
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null,
Bool(bool),
Number(f64),
Str(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}
// Approach 1: Pretty-print with indentation (recursive, builds String)
fn escape_string(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
'\r' => out.push_str("\\r"),
c => out.push(c),
}
}
out
}
fn pretty_print(j: &JsonValue, indent: usize) -> String {
let pad = " ".repeat(indent * 2);
let pad2 = " ".repeat((indent + 1) * 2);
match j {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(true) => "true".to_string(),
JsonValue::Bool(false) => "false".to_string(),
JsonValue::Number(n) => {
if n.fract() == 0.0 && n.is_finite() {
format!("{}", *n as i64)
} else {
format!("{}", n)
}
}
JsonValue::Str(s) => format!("\"{}\"", escape_string(s)),
JsonValue::Array(items) if items.is_empty() => "[]".to_string(),
JsonValue::Array(items) => {
let inner: Vec<String> = items
.iter()
.map(|item| format!("{}{}", pad2, pretty_print(item, indent + 1)))
.collect();
format!("[\n{}\n{}]", inner.join(",\n"), pad)
}
JsonValue::Object(pairs) if pairs.is_empty() => "{}".to_string(),
JsonValue::Object(pairs) => {
let inner: Vec<String> = pairs
.iter()
.map(|(k, v)| {
format!(
"{}\"{}\": {}",
pad2,
escape_string(k),
pretty_print(v, indent + 1)
)
})
.collect();
format!("{{\n{}\n{}}}", inner.join(",\n"), pad)
}
}
}
// Approach 2: Compact (single-line) printer
fn compact(j: &JsonValue) -> String {
match j {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => {
if n.fract() == 0.0 && n.is_finite() {
format!("{}", *n as i64)
} else {
format!("{}", n)
}
}
JsonValue::Str(s) => format!("\"{}\"", escape_string(s)),
JsonValue::Array(items) => {
let inner: Vec<String> = items.iter().map(compact).collect();
format!("[{}]", inner.join(","))
}
JsonValue::Object(pairs) => {
let inner: Vec<String> = pairs
.iter()
.map(|(k, v)| format!("\"{}\":{}", escape_string(k), compact(v)))
.collect();
format!("{{{}}}", inner.join(","))
}
}
}
fn main() {
let json = JsonValue::Object(vec![
("name".to_string(), JsonValue::Str("Alice".to_string())),
("age".to_string(), JsonValue::Number(30.0)),
(
"scores".to_string(),
JsonValue::Array(vec![
JsonValue::Number(95.0),
JsonValue::Number(87.0),
JsonValue::Number(92.0),
]),
),
(
"address".to_string(),
JsonValue::Object(vec![
("city".to_string(), JsonValue::Str("Amsterdam".to_string())),
("zip".to_string(), JsonValue::Str("1234AB".to_string())),
]),
),
("active".to_string(), JsonValue::Bool(true)),
("note".to_string(), JsonValue::Null),
]);
println!("=== Pretty ===");
println!("{}", pretty_print(&json, 0));
println!("\n=== Compact ===");
println!("{}", compact(&json));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_primitives() {
assert_eq!(pretty_print(&JsonValue::Null, 0), "null");
assert_eq!(pretty_print(&JsonValue::Bool(true), 0), "true");
assert_eq!(pretty_print(&JsonValue::Bool(false), 0), "false");
assert_eq!(pretty_print(&JsonValue::Number(42.0), 0), "42");
assert_eq!(pretty_print(&JsonValue::Str("hi".into()), 0), "\"hi\"");
}
#[test]
fn test_escape() {
let s = JsonValue::Str("hello \"world\"\nnewline".to_string());
assert_eq!(pretty_print(&s, 0), "\"hello \\\"world\\\"\\nnewline\"");
}
#[test]
fn test_empty_array_object() {
assert_eq!(pretty_print(&JsonValue::Array(vec![]), 0), "[]");
assert_eq!(pretty_print(&JsonValue::Object(vec![]), 0), "{}");
}
#[test]
fn test_compact_no_newlines() {
let json = JsonValue::Object(vec![
("a".to_string(), JsonValue::Number(1.0)),
("b".to_string(), JsonValue::Bool(false)),
]);
let c = compact(&json);
assert!(!c.contains('\n'));
assert!(c.contains("\"a\":1"));
assert!(c.contains("\"b\":false"));
}
#[test]
fn test_nested_pretty() {
let json = JsonValue::Array(vec![
JsonValue::Number(1.0),
JsonValue::Array(vec![JsonValue::Number(2.0), JsonValue::Number(3.0)]),
]);
let p = pretty_print(&json, 0);
assert!(p.contains('\n'));
assert!(p.starts_with('['));
assert!(p.ends_with(']'));
}
}
(* 956: JSON Pretty Print *)
type json =
| Null
| Bool of bool
| Number of float
| Str of string
| Array of json list
| Object of (string * json) list
(* Approach 1: Simple recursive pretty-printer with indentation *)
let escape_string s =
let buf = Buffer.create (String.length s + 2) in
String.iter (fun c ->
match c with
| '"' -> Buffer.add_string buf "\\\""
| '\\' -> Buffer.add_string buf "\\\\"
| '\n' -> Buffer.add_string buf "\\n"
| '\t' -> Buffer.add_string buf "\\t"
| '\r' -> Buffer.add_string buf "\\r"
| c -> Buffer.add_char buf c
) s;
Buffer.contents buf
let rec pretty_print ?(indent=0) j =
let pad = String.make (indent * 2) ' ' in
let pad2 = String.make ((indent + 1) * 2) ' ' in
match j with
| Null -> "null"
| Bool true -> "true"
| Bool false -> "false"
| Number n ->
if Float.is_integer n then string_of_int (int_of_float n)
else Printf.sprintf "%g" n
| Str s -> Printf.sprintf "\"%s\"" (escape_string s)
| Array [] -> "[]"
| Array items ->
let items_str = List.map (fun item ->
pad2 ^ pretty_print ~indent:(indent+1) item
) items in
"[\n" ^ String.concat ",\n" items_str ^ "\n" ^ pad ^ "]"
| Object [] -> "{}"
| Object pairs ->
let pairs_str = List.map (fun (k, v) ->
pad2 ^ Printf.sprintf "\"%s\": %s" (escape_string k) (pretty_print ~indent:(indent+1) v)
) pairs in
"{\n" ^ String.concat ",\n" pairs_str ^ "\n" ^ pad ^ "}"
(* Approach 2: Compact (single-line) printer *)
let rec compact j =
match j with
| Null -> "null"
| Bool true -> "true"
| Bool false -> "false"
| Number n ->
if Float.is_integer n then string_of_int (int_of_float n)
else Printf.sprintf "%g" n
| Str s -> Printf.sprintf "\"%s\"" (escape_string s)
| Array items ->
"[" ^ String.concat "," (List.map compact items) ^ "]"
| Object pairs ->
"{" ^ String.concat "," (List.map (fun (k,v) ->
Printf.sprintf "\"%s\":%s" k (compact v)
) pairs) ^ "}"
let () =
let json = Object [
("name", Str "Alice");
("age", Number 30.0);
("scores", Array [Number 95.0; Number 87.0; Number 92.0]);
("address", Object [("city", Str "Amsterdam"); ("zip", Str "1234AB")]);
("active", Bool true);
("note", Null);
] in
let pretty = pretty_print json in
assert (String.length pretty > 0);
assert (String.sub pretty 0 1 = "{");
let c = compact json in
assert (String.length c > 0);
assert (not (String.contains c '\n'));
(* Test escape *)
let with_special = Str "hello \"world\"\nnewline" in
let escaped = pretty_print with_special in
assert (escaped = "\"hello \\\"world\\\"\\nnewline\"");
Printf.printf "%s\n" pretty;
Printf.printf "โ All tests passed\n"