///|
/// Why JSON could not be read the way the caller expected. Field access
/// errors name the field; parse errors carry the underlying position detail.
pub(all) suberror JsonError {
/// The text is not valid JSON.
Invalid(String)
/// The object does not contain the field.
Missing(String)
/// The field exists but is not the requested type.
WrongType(String, String)
}
///|
pub impl Show for JsonError with fn output(self : JsonError, logger : &Logger) -> Unit {
match self {
Invalid(detail) => logger.write_string("invalid json: \{detail}")
Missing(key) => logger.write_string("field \"\{key}\" is missing")
WrongType(key, expected) =>
logger.write_string("field \"\{key}\" is not \{expected}")
}
}
///|
pub extend JsonError with Show::{output, to_string}
///|
/// Parse JSON text, with the parse failure re-wrapped as `JsonError::Invalid`.
pub fn parse_json(text : StringView) -> Json raise JsonError {
@json.parse(text) catch {
e => raise Invalid("\{e}")
}
}
///|
/// Parse JSON text, returning `None` instead of raising on malformed input.
pub fn try_parse_json(text : StringView) -> Json? {
try @json.parse(text) catch {
_ => None
} noraise {
json => Some(json)
}
}
///|
/// Serialize a JSON value (compact form).
pub fn encode_json(json : Json) -> String {
json.stringify()
}
///|
fn field(json : Json, key : String) -> Json raise JsonError {
match json {
Object(fields) =>
match fields.get(key) {
Some(value) => value
None => raise Missing(key)
}
_ => raise Missing(key)
}
}
///|
/// The string value of an object field.
pub fn string_field(json : Json, key : String) -> String raise JsonError {
match field(json, key) {
String(value) => value
_ => raise WrongType(key, "string")
}
}
///|
/// The integer value of an object field. JSON numbers that carry a fraction
/// are rejected rather than truncated.
pub fn int_field(json : Json, key : String) -> Int raise JsonError {
let number = match field(json, key) {
Number(number, ..) => number
_ => raise WrongType(key, "number")
}
if number != number.trunc() {
raise WrongType(key, "integer")
}
number.to_int()
}
///|
/// The boolean value of an object field.
pub fn bool_field(json : Json, key : String) -> Bool raise JsonError {
match field(json, key) {
True => true
False => false
_ => raise WrongType(key, "bool")
}
}
///|
/// The array value of an object field.
pub fn array_field(json : Json, key : String) -> Array[Json] raise JsonError {
match field(json, key) {
Array(value) => value
_ => raise WrongType(key, "array")
}
}
///|
/// The nested object value of an object field, kept as JSON so field reads
/// chain: `int_field(object_field(json, "amount"), "total")`.
pub fn object_field(json : Json, key : String) -> Json raise JsonError {
let value = field(json, key)
match value {
Object(_) => value
_ => raise WrongType(key, "object")
}
}