///|
priv struct JsonParser {
src : String
mut pos : Int
sort_keys : Bool
}
///|
fn JsonParser::error(self : JsonParser, msg : String) -> TemplateError {
TemplateError::new(
CannotDeserialize,
"invalid JSON at offset \{self.pos}: \{msg}",
)
}
///|
fn JsonParser::skip_ws(self : JsonParser) -> Unit {
while self.pos < self.src.length() {
match self.src[self.pos] {
' ' | '\t' | '\n' | '\r' => self.pos += 1
_ => break
}
}
}
///|
fn JsonParser::expect(
self : JsonParser,
lit : String,
) -> Unit raise TemplateError {
if starts_with_at(self.src, self.pos, lit) {
self.pos += lit.length()
} else {
raise self.error("expected \{lit}")
}
}
///|
fn JsonParser::parse_value(self : JsonParser) -> Value raise TemplateError {
self.skip_ws()
if self.pos >= self.src.length() {
raise self.error("unexpected end of input")
}
match self.src[self.pos] {
'{' => {
self.pos += 1
let pairs : Array[(String, Value)] = []
self.skip_ws()
if unit_at(self.src, self.pos) == '}'.to_int() {
self.pos += 1
} else {
for ;; {
self.skip_ws()
let key = self.parse_string()
self.skip_ws()
self.expect(":")
let value = self.parse_value()
pairs.push((key, value))
self.skip_ws()
if unit_at(self.src, self.pos) == ','.to_int() {
self.pos += 1
} else {
self.expect("}")
break
}
}
}
if self.sort_keys {
pairs.sort_by((a, b) => compare_str(a.0, b.0))
}
let m : Map[Value, Value] = Map([])
for pair in pairs {
m[Value::from_string(pair.0)] = pair.1
}
Value::from_map(m)
}
'[' => {
self.pos += 1
let items = []
self.skip_ws()
if unit_at(self.src, self.pos) == ']'.to_int() {
self.pos += 1
} else {
for ;; {
items.push(self.parse_value())
self.skip_ws()
if unit_at(self.src, self.pos) == ','.to_int() {
self.pos += 1
} else {
self.expect("]")
break
}
}
}
Value::from_array(items)
}
'"' => Value::from_string(self.parse_string())
't' => {
self.expect("true")
Value::from_bool(true)
}
'f' => {
self.expect("false")
Value::from_bool(false)
}
'n' => {
self.expect("null")
Value::none()
}
_ => self.parse_number()
}
}
///|
fn JsonParser::parse_string(self : JsonParser) -> String raise TemplateError {
if unit_at(self.src, self.pos) != '"'.to_int() {
raise self.error("expected string")
}
self.pos += 1
let sb = StringBuilder()
for ;; {
if self.pos >= self.src.length() {
raise self.error("unterminated string")
}
let c = self.src[self.pos]
self.pos += 1
match c {
'"' => break
'\\' => {
let e = unit_at(self.src, self.pos)
self.pos += 1
match e.unsafe_to_char() {
'"' => sb.write_char('"')
'\\' => sb.write_char('\\')
'/' => sb.write_char('/')
'b' => sb.write_char('\u{08}')
'f' => sb.write_char('\u{0C}')
'n' => sb.write_char('\n')
'r' => sb.write_char('\r')
't' => sb.write_char('\t')
'u' => {
let mut cp = 0
for _ in 0..<4 {
let h = hex_value(unit_at(self.src, self.pos).unsafe_to_char())
if h < 0 {
raise self.error("invalid unicode escape")
}
cp = cp * 16 + h
self.pos += 1
}
if cp >= 0xD800 &&
cp <= 0xDBFF &&
starts_with_at(self.src, self.pos, "\\u") {
let save = self.pos
self.pos += 2
let mut lo = 0
let mut ok = true
for _ in 0..<4 {
let h = hex_value(unit_at(self.src, self.pos).unsafe_to_char())
if h < 0 {
ok = false
break
}
lo = lo * 16 + h
self.pos += 1
}
if ok && lo >= 0xDC00 && lo <= 0xDFFF {
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
} else {
self.pos = save
}
}
if cp >= 0xD800 && cp <= 0xDFFF {
raise self.error("lone leading surrogate in hex escape")
}
sb.write_char(cp.unsafe_to_char())
}
_ => raise self.error("invalid escape")
}
}
c => {
let u = c.to_int()
if u < 0x20 {
raise self.error("control character in string")
}
if u >= 0xD800 && u <= 0xDBFF {
// a surrogate pair in the source text
let lo = unit_at(self.src, self.pos)
if lo >= 0xDC00 && lo <= 0xDFFF {
self.pos += 1
sb.write_char(
(0x10000 + ((u - 0xD800) << 10) + (lo - 0xDC00)).unsafe_to_char(),
)
continue
}
raise self.error("invalid unicode code point")
}
if u >= 0xDC00 && u <= 0xDFFF {
raise self.error("invalid unicode code point")
}
sb.write_char(u.unsafe_to_char())
}
}
}
sb.to_string()
}
///|
fn JsonParser::parse_number(self : JsonParser) -> Value raise TemplateError {
// JSON grammar: -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?
let start = self.pos
let negative = unit_at(self.src, self.pos) == '-'.to_int()
if negative {
self.pos += 1
}
let digits_start = self.pos
if unit_at(self.src, self.pos) == '0'.to_int() {
self.pos += 1
} else if is_digit_unit(unit_at(self.src, self.pos)) {
while is_digit_unit(unit_at(self.src, self.pos)) {
self.pos += 1
}
} else {
raise self.error("invalid number")
}
let int_end = self.pos
let mut is_float = false
if unit_at(self.src, self.pos) == '.'.to_int() {
is_float = true
self.pos += 1
if !is_digit_unit(unit_at(self.src, self.pos)) {
raise self.error("invalid number")
}
while is_digit_unit(unit_at(self.src, self.pos)) {
self.pos += 1
}
}
let e = unit_at(self.src, self.pos)
if e == 'e'.to_int() || e == 'E'.to_int() {
is_float = true
self.pos += 1
let sign = unit_at(self.src, self.pos)
if sign == '+'.to_int() || sign == '-'.to_int() {
self.pos += 1
}
if !is_digit_unit(unit_at(self.src, self.pos)) {
raise self.error("invalid number")
}
while is_digit_unit(unit_at(self.src, self.pos)) {
self.pos += 1
}
}
let text = self.src.view(start_offset=start, end_offset=self.pos).to_owned()
if !is_float {
guard parse_uint_radix(
self.src.view(start_offset=digits_start, end_offset=int_end),
10,
)
is Some(magnitude) else {
raise self.error("invalid number")
}
if negative {
// like serde_json, `-0` becomes a negative zero float
if magnitude.is_zero() {
return Value::from_double(-0.0)
}
let v = -magnitude
if in_i64(v) {
return Value::from_int64(v.to_int64())
}
} else if magnitude <= max_u64_big {
return Value::from_uint64(magnitude.to_uint64())
}
}
let f = @string.parse_double(text) catch {
_ => raise self.error("number out of range")
}
Value::from_double(f)
}
///|
/// Parses a JSON document into a value.
///
/// Unlike `@json.parse` this preserves the distinction between integers
/// (`4`) and floats (`4.0`). With `sort_keys` set, object keys are sorted
/// (this matches the behavior of `serde_json` without `preserve_order`).
pub fn Value::from_json_str(
s : String,
sort_keys? : Bool = false,
) -> Value raise TemplateError {
let p = { src: s, pos: 0, sort_keys, }
let rv = p.parse_value()
p.skip_ws()
if p.pos != s.length() {
raise p.error("trailing characters")
}
rv
}
///|
/// Converts a MoonBit `Json` value into a value.
pub fn Value::from_json(j : Json) -> Value {
match j {
Null => Value::none()
True => Value::from_bool(true)
False => Value::from_bool(false)
Number(n, ..) =>
if n == n.trunc() && n.abs() < 9007199254740992.0 {
Value::from_int64(n.to_int64())
} else {
Value::from_double(n)
}
String(s) => Value::from_string(s)
Array(items) => Value::from_array(items.map(Value::from_json))
Object(m) => {
let rv : Map[Value, Value] = Map([])
for k, v in m {
rv[Value::from_string(k)] = Value::from_json(v)
}
Value::from_map(rv)
}
}
}
///|
/// JSON output styles.
priv enum JsonStyle {
/// serde_json's compact formatter (`[1,2]`)
Compact
/// MiniJinja's `tojson` formatter (`[1, 2]`)
Spaced
/// serde_json's pretty formatter with the given indentation
Pretty(String)
}
///|
/// Formats a float like `serde_json` (which uses `ryu`).
fn json_float(f : Double) -> String {
if f.is_nan() || f.is_inf() {
return "null"
}
let s = @rfmt.f64_display(f)
if f == 0.0 {
return s + ".0"
}
let neg = f < 0.0
let digits = StringBuilder()
let mut point = 0
// reconstruct digits and decimal point position from the plain notation
let body = if neg { s.view(start_offset=1).to_owned() } else { s }
let int_part = match body.find(".") {
Some(idx) => body.view(end_offset=idx).to_owned()
None => body
}
let frac_part = match body.find(".") {
Some(idx) => body.view(start_offset=idx + 1).to_owned()
None => ""
}
let all = int_part + frac_part
let mut lead = 0
while lead < all.length() && all[lead] == '0' {
lead += 1
}
let mut trail = all.length()
while trail > lead && all[trail - 1] == '0' {
trail -= 1
}
digits.write_view(all.view(start_offset=lead, end_offset=trail))
point = int_part.length() - lead
let d = digits.to_string()
let len = d.length()
let sign = if neg { "-" } else { "" }
if point >= len && point <= 16 {
sign + d + "0".repeat(point - len) + ".0"
} else if point > 0 && point <= 16 {
sign +
d.view(end_offset=point).to_owned() +
"." +
d.view(start_offset=point).to_owned()
} else if point > -5 && point <= 0 {
sign + "0." + "0".repeat(-point) + d
} else {
let exp = point - 1
let mantissa = if len == 1 {
d
} else {
d.view(end_offset=1).to_owned() + "." + d.view(start_offset=1).to_owned()
}
sign + mantissa + "e" + exp.to_string()
}
}
///|
fn write_json_string(sb : StringBuilder, s : String) -> Unit {
sb.write_char('"')
for c in s {
match c {
'"' => sb.write_string("\\\"")
'\\' => sb.write_string("\\\\")
'\n' => sb.write_string("\\n")
'\r' => sb.write_string("\\r")
'\t' => sb.write_string("\\t")
'\u{08}' => sb.write_string("\\b")
'\u{0C}' => sb.write_string("\\f")
c =>
if c.to_int() < 0x20 {
let hex = c.to_int().to_string(radix=16)
sb.write_string("\\u00")
if hex.length() < 2 {
sb.write_char('0')
}
sb.write_string(hex)
} else {
sb.write_char(c)
}
}
}
sb.write_char('"')
}
///|
fn write_json(
sb : StringBuilder,
value : Value,
style : JsonStyle,
depth : Int,
) -> Unit raise TemplateError {
fn newline(sb : StringBuilder, style : JsonStyle, depth : Int) -> Unit {
if style is Pretty(indent) {
sb.write_char('\n')
for _ in 0.. ","
Spaced => ", "
}
let key_sep = match style {
Compact => ":"
Spaced | Pretty(_) => ": "
}
match value {
Bool(b) => sb.write_string(if b { "true" } else { "false" })
U64(v) => sb.write_string(v.to_string())
I64(v) => sb.write_string(v.to_string())
U128(v) | I128(v) => sb.write_string(v.to_string())
F64(f) => sb.write_string(json_float(f))
NoneValue | Undefined(_) | Invalid(_) => sb.write_string("null")
Str(s, _) => write_json_string(sb, s)
Bytes(b) => {
sb.write_char('[')
let mut first = true
for byte in b {
if !first {
sb.write_string(item_sep)
}
first = false
newline(sb, style, depth + 1)
sb.write_string(byte.to_int().to_string())
}
if !first {
newline(sb, style, depth)
}
sb.write_char(']')
}
Object(obj) =>
match obj.repr() {
Plain => write_json_string(sb, value.to_string())
Seq | Iterable => {
sb.write_char('[')
let mut first = true
if obj.try_iter() is Some(iter) {
for item in iter {
if !first {
sb.write_string(item_sep)
}
first = false
newline(sb, style, depth + 1)
write_json(sb, item, style, depth + 1)
}
}
if !first {
newline(sb, style, depth)
}
sb.write_char(']')
}
Map => {
sb.write_char('{')
let mut first = true
if obj.try_iter_pairs() is Some(iter) {
for pair in iter {
let (k, v) = pair
if !first {
sb.write_string(item_sep)
}
first = false
newline(sb, style, depth + 1)
// serde_json's map key serializer
match k {
Str(s, _) => write_json_string(sb, s)
U64(_) | I64(_) | U128(_) | I128(_) =>
write_json_string(sb, k.to_string())
Bool(b) =>
write_json_string(sb, if b { "true" } else { "false" })
F64(f) if !f.is_nan() && !f.is_inf() =>
write_json_string(sb, json_float(f))
Object(o) if o.repr() is Plain =>
write_json_string(sb, k.to_string())
F64(_) =>
raise TemplateError::new(
BadSerialization,
"float key must be finite (got NaN or +/-inf)",
)
_ =>
raise TemplateError::new(
BadSerialization,
"key must be a string",
)
}
sb.write_string(key_sep)
write_json(sb, v, style, depth + 1)
}
}
if !first {
newline(sb, style, depth)
}
sb.write_char('}')
}
}
}
}
///|
fn value_to_json_with_style(
value : Value,
style : JsonStyle,
) -> String raise TemplateError {
let sb = StringBuilder()
write_json(sb, value, style, 0)
sb.to_string()
}
///|
/// Serializes the value to JSON. With `indent` the output is pretty
/// printed.
pub fn Value::to_json_string(
self : Value,
indent? : Int,
) -> String raise TemplateError {
match indent {
Some(n) => value_to_json_with_style(self, Pretty(" ".repeat(n)))
None => value_to_json_with_style(self, Compact)
}
}