// Reading and writing the JSON shapes the protocol shares: optional fields that vanish when unset,
// arrays of strings, integers that arrive as numbers or as strings, and a timestamp in UTC.
///|
/// An empty object, ready for fields to be put into.
fn object() -> Map[String, Json] {
Map([])
}
///|
/// Put `value` under `key` when it is set; an unset optional field is absent, not null.
fn put_str(fields : Map[String, Json], key : String, value : String?) -> Unit {
match value {
Some(s) => fields[key] = Json::string(s)
None => ()
}
}
///|
/// Put `value` under `key` when it is set.
fn put_json(fields : Map[String, Json], key : String, value : Json?) -> Unit {
match value {
Some(json) => fields[key] = json
None => ()
}
}
///|
/// Put `value` under `key` when it is set.
fn put_int(fields : Map[String, Json], key : String, value : Int32?) -> Unit {
match value {
Some(n) => fields[key] = Json::number(n.to_double(), repr=n.to_string())
None => ()
}
}
///|
/// Put `values` under `key` when it is set.
fn put_strs(
fields : Map[String, Json],
key : String,
values : Array[String]?,
) -> Unit {
match values {
Some(xs) => fields[key] = strings(xs)
None => ()
}
}
///|
/// A JSON array of strings.
fn strings(values : Array[String]) -> Json {
Json::array(values.map(fn(s) { Json::string(s) }))
}
///|
/// The string under `key`, or none when absent or of another type.
fn opt_str(fields : Map[String, Json], key : String) -> String? {
match fields.get(key) {
Some(String(s)) => Some(s)
_ => None
}
}
///|
/// The object under `key`, or none when absent or of another type.
fn opt_object(fields : Map[String, Json], key : String) -> Map[String, Json]? {
match fields.get(key) {
Some(Object(o)) => Some(o)
_ => None
}
}
///|
/// The boolean under `key`, or none when absent or of another type.
fn opt_bool(fields : Map[String, Json], key : String) -> Bool? {
match fields.get(key) {
Some(True) => Some(true)
Some(False) => Some(false)
_ => None
}
}
///|
/// The integer under `key`, or none when absent. ProtoJSON writes 64-bit integers as strings, so a
/// quoted integer is read as one.
fn opt_int(fields : Map[String, Json], key : String) -> Int32? {
match fields.get(key) {
Some(Number(n, ..)) => Some(n.to_int())
Some(String(s)) => Some(@string.parse_int(s)) catch { _ => None }
_ => None
}
}
///|
/// The string array under `key`, or none when absent. Entries that are not strings are dropped.
fn opt_strs(fields : Map[String, Json], key : String) -> Array[String]? {
match fields.get(key) {
Some(Array(items)) => {
let out : Array[String] = []
for item in items {
if item is String(s) {
out.push(s)
}
}
Some(out)
}
_ => None
}
}
///|
/// The string array under `key`, empty when absent.
fn strs_or_empty(fields : Map[String, Json], key : String) -> Array[String] {
match opt_strs(fields, key) {
Some(xs) => xs
None => []
}
}
///|
/// The value under either spelling of a name. The protocol writes camelCase; a client that sends
/// the proto field name instead is understood rather than refused, which section 5.7 asks for.
fn pick(fields : Map[String, Json], camel : String, snake : String) -> Json? {
match fields.get(camel) {
Some(value) => Some(value)
None => fields.get(snake)
}
}
///|
/// The string under either spelling of a name.
fn pick_str(
fields : Map[String, Json],
camel : String,
snake : String,
) -> String? {
match pick(fields, camel, snake) {
Some(String(s)) => Some(s)
_ => None
}
}
///|
/// The integer under either spelling of a name.
fn pick_int(
fields : Map[String, Json],
camel : String,
snake : String,
) -> Int32? {
match pick(fields, camel, snake) {
Some(Number(n, ..)) => Some(n.to_int())
Some(String(s)) => Some(@string.parse_int(s)) catch { _ => None }
_ => None
}
}
///|
/// The boolean under either spelling of a name.
fn pick_bool(
fields : Map[String, Json],
camel : String,
snake : String,
) -> Bool? {
match pick(fields, camel, snake) {
Some(True) => Some(true)
Some(False) => Some(false)
_ => None
}
}
///|
/// The object under either spelling of a name.
fn pick_object(
fields : Map[String, Json],
camel : String,
snake : String,
) -> Map[String, Json]? {
match pick(fields, camel, snake) {
Some(Object(o)) => Some(o)
_ => None
}
}
///|
/// An optional field of the wrong shape has no reading; say which field it was.
fn wrong_type(
path : @json.JsonPath,
key : String,
want : String,
) -> @json.JsonDecodeError {
@json.JsonDecodeError((path.add_key(key), "want \{want}"))
}
///|
/// The object that `json` must be, or a decode error naming the path.
fn as_object(
json : Json,
path : @json.JsonPath,
) -> Map[String, Json] raise @json.JsonDecodeError {
match json {
Object(fields) => fields
_ => raise @json.JsonDecodeError((path, "want an object"))
}
}
///|
/// The required string under `key`.
fn need_str(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
) -> String raise @json.JsonDecodeError {
match opt_str(fields, key) {
Some(s) => s
None => raise wrong_type(path, key, "a string")
}
}
///|
/// The required array under `key`.
fn need_array(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
) -> Array[Json] raise @json.JsonDecodeError {
match fields.get(key) {
Some(Array(items)) => items
_ => raise wrong_type(path, key, "an array")
}
}
///|
/// Read an optional object-valued field with `read`, keeping it absent when the field is.
fn[A] read_opt(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
read : (Json, @json.JsonPath) -> A raise @json.JsonDecodeError,
) -> A? raise @json.JsonDecodeError {
match fields.get(key) {
Some(json) => Some(read(json, path.add_key(key)))
None => None
}
}
///|
/// Read an array-valued field with `read`, absent when the field is.
fn[A] read_opt_array(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
read : (Json, @json.JsonPath) -> A raise @json.JsonDecodeError,
) -> Array[A]? raise @json.JsonDecodeError {
match fields.get(key) {
Some(Array(items)) => {
let out : Array[A] = []
let mut index = 0
for item in items {
out.push(read(item, path.add_key(key).add_index(index)))
index = index + 1
}
Some(out)
}
None => None
_ => raise wrong_type(path, key, "an array")
}
}
///|
/// Read a required array-valued field with `read`.
fn[A] read_array(
fields : Map[String, Json],
key : String,
path : @json.JsonPath,
read : (Json, @json.JsonPath) -> A raise @json.JsonDecodeError,
) -> Array[A] raise @json.JsonDecodeError {
let items = need_array(fields, key, path)
let out : Array[A] = []
let mut index = 0
for item in items {
out.push(read(item, path.add_key(key).add_index(index)))
index = index + 1
}
out
}