///|
fn newline(out : StringBuilder, pretty : Bool, depth : Int32) -> Unit {
if pretty {
out.write_char('\n')
out.write_string(" ".repeat(depth))
}
}
///|
fn write_value(
out : StringBuilder,
value : @value.Value,
pretty : Bool,
depth : Int32,
) -> Unit raise JsonError {
match value {
Null => out.write_string("null")
Bool(b) => out.write_string(if b { "true" } else { "false" })
Int(n) => out.write_string(n.to_string())
BigInt(n) => out.write_string(n.to_string())
Decimal(d) => {
// Always retain an exponent, even zero, to preserve Decimal vs Int and
// the exact coefficient, scale and sign of zero on read-back.
if d.is_negative_zero() {
out.write_char('-')
}
out.write_string(d.coefficient().to_string())
out.write_char('e')
out.write_string(d.exponent().to_string())
}
Float(f) => {
if f.is_nan() || f.is_inf() {
raise Unsupported("non-finite Float")
}
// Core Json.stringify normalizes negative zero to 0.
out.write_string(
if f == 0.0 && f.reinterpret_as_int64() < 0L {
"-0.0"
} else {
Json::number(f).stringify()
},
)
}
String(s) => out.write_string(Json::string(s).stringify())
List(items) => {
if depth >= 256 {
raise Unsupported("nesting exceeds 256 containers")
}
out.write_char('[')
for i, item in items {
if i > 0 {
out.write_char(',')
}
newline(out, pretty, depth + 1)
write_value(out, item, pretty, depth + 1)
}
if !items.is_empty() {
newline(out, pretty, depth)
}
out.write_char(']')
}
Tuple(t) => {
if depth >= 256 {
raise Unsupported("nesting exceeds 256 containers")
}
out.write_char('{')
let fields = t.fields()
for i, field in fields {
if i > 0 {
out.write_char(',')
}
newline(out, pretty, depth + 1)
out.write_string(Json::string(field.0).stringify())
out.write_char(':')
if pretty {
out.write_char(' ')
}
write_value(out, field.1, pretty, depth + 1)
}
if !fields.is_empty() {
newline(out, pretty, depth)
}
out.write_char('}')
}
Missing => raise Unsupported("Missing")
Bag(_) =>
raise Unsupported("Bag; explicitly convert the query result to a list")
Map(_) => raise Unsupported("Map")
Graph(_) => raise Unsupported("Graph")
Date(_) => raise Unsupported("Date")
Time(_) => raise Unsupported("Time")
Timestamp(_) => raise Unsupported("Timestamp")
IntervalYM(_) => raise Unsupported("IntervalYM")
IntervalDT(_) => raise Unsupported("IntervalDT")
Blob(_) => raise Unsupported("Blob")
Clob(_) => raise Unsupported("Clob")
Ion(_) => raise Unsupported("boxed Ion")
}
}
///|
/// Write one JSON document, without a trailing newline. Duplicate tuple fields
/// are emitted in order. Decimals retain their scale. Finite floats use the
/// shortest round-trippable binary64 spelling; reading it gives Int/Decimal,
/// not Float. Other PartiQL types and non-finite floats raise Unsupported.
/// At most 256 nested containers are supported, matching read_text.
pub fn write_text(
value : @value.Value,
pretty~ : Bool = false,
) -> String raise JsonError {
let out = StringBuilder()
write_value(out, value, pretty, 0)
out.to_string()
}
///|
/// Write one JSON document as UTF-8, without a BOM or trailing newline.
pub fn write_bytes(
value : @value.Value,
pretty~ : Bool = false,
) -> Bytes raise JsonError {
@utf8.encode(write_text(value, pretty~))
}