///|
/// Render a parsed value as deterministic human-readable JSON. Unlike
/// `canonicalize`, this adds indentation and line breaks for review output.
pub fn pretty_json(
value : JsonValue,
indent_width : Int,
) -> Result[String, CanonicalError] {
let width = if indent_width < 0 { 0 } else { indent_width }
pretty_value(value, 0, width)
}
///|
pub fn pretty_document(
input : String,
indent_width : Int,
) -> Result[String, String] {
let value = match parse(input) {
Ok(value) => value
Err(error) => return Err(error.message())
}
match pretty_json(value, indent_width) {
Ok(output) => Ok(output)
Err(error) => Err(error.message())
}
}
///|
fn pretty_value(
value : JsonValue,
depth : Int,
width : Int,
) -> Result[String, CanonicalError] {
match value {
Null => Ok("null")
Bool(value) => Ok(if value { "true" } else { "false" })
Number(value) => canonical_number(value)
String(value) => Ok(canonical_string(value))
Array(values) => pretty_array(values, depth, width)
Object(entries) => pretty_object(entries, depth, width)
}
}
///|
fn pretty_array(
values : Array[JsonValue],
depth : Int,
width : Int,
) -> Result[String, CanonicalError] {
if values.length() == 0 {
return Ok("[]")
}
if width == 0 {
let mut compact = "["
let mut first = true
for value in values {
if !first {
compact = compact + ","
}
first = false
let rendered = match pretty_value(value, depth + 1, width) {
Ok(rendered) => rendered
Err(error) => return Err(error)
}
compact = compact + rendered
}
return Ok(compact + "]")
}
let mut output = "[\n"
let mut first = true
for value in values {
if !first {
output = output + ",\n"
}
first = false
output = output + indentation(depth + 1, width)
let rendered = match pretty_value(value, depth + 1, width) {
Ok(rendered) => rendered
Err(error) => return Err(error)
}
output = output + rendered
}
Ok(output + "\n" + indentation(depth, width) + "]")
}
///|
fn pretty_object(
entries : Array[(String, JsonValue)],
depth : Int,
width : Int,
) -> Result[String, CanonicalError] {
if entries.length() == 0 {
return Ok("{}")
}
let ordered : Array[(String, JsonValue)] = []
for entry in entries {
ordered.push(entry)
}
sort_entries(ordered)
if width == 0 {
let mut compact = "{"
let mut first = true
for entry in ordered {
if !first {
compact = compact + ","
}
first = false
let value = match pretty_value(entry.1, depth + 1, width) {
Ok(value) => value
Err(error) => return Err(error)
}
compact = compact + canonical_string(entry.0) + ":" + value
}
return Ok(compact + "}")
}
let mut output = "{\n"
let mut first = true
for entry in ordered {
if !first {
output = output + ",\n"
}
first = false
let value = match pretty_value(entry.1, depth + 1, width) {
Ok(value) => value
Err(error) => return Err(error)
}
output = output +
indentation(depth + 1, width) +
canonical_string(entry.0) +
": " +
value
}
Ok(output + "\n" + indentation(depth, width) + "}")
}
///|
fn indentation(depth : Int, width : Int) -> String {
let mut output = ""
let mut count = 0
while count < depth * width {
output = output + " "
count = count + 1
}
output
}