///|
fn json_type_name(json : Json) -> String {
  match json {
    Null => "null"
    True | False => "bool"
    Number(_) => "number"
    String(_) => "string"
    Array(_) => "array"
    Object(_) => "object"
  }
}

///|
fn json_field(json : Json, key : String) -> Json? {
  match json {
    Object(obj) => obj.get(key)
    _ => None
  }
}

///|
fn json_array_field(json : Json, key : String) -> Array[Json] {
  match json_field(json, key) {
    Some(Array(items)) => items
    _ => []
  }
}

///|
fn json_object_pairs(value : Json?) -> Array[(String, Json)] {
  match value {
    Some(Object(obj)) => obj.to_array()
    _ => []
  }
}

///|
fn json_string_value(value : Json?, default? : String = "") -> String {
  match value {
    Some(v) => (@json.from_json(v) : String) catch { _ => default }
    None => default
  }
}

///|
fn json_int_value(value : Json?, default? : Int = 0) -> Int {
  match value {
    Some(v) => (@json.from_json(v) : Int) catch { _ => default }
    None => default
  }
}

///|
fn json_bool_value(value : Json?, default? : Bool = false) -> Bool {
  match value {
    Some(v) => (@json.from_json(v) : Bool) catch { _ => default }
    None => default
  }
}

///|
fn json_rect_value(value : Json?) -> Rect {
  match value {
    Some(v) =>
      match v {
        Object(_) =>
          Rect::new(
            json_int_value(json_field(v, "x")),
            json_int_value(json_field(v, "y")),
            json_int_value(json_field(v, "w")),
            json_int_value(json_field(v, "h")),
          )
        _ => Rect::empty()
      }
    _ => Rect::empty()
  }
}

///|
fn json_size_value(value : Json?) -> Size {
  match value {
    Some(v) =>
      match v {
        Object(_) =>
          Size::new(
            json_int_value(json_field(v, "w")),
            json_int_value(json_field(v, "h")),
          )
        _ => Size::empty()
      }
    _ => Size::empty()
  }
}

///|
fn json_index_path(parent : String, index : Int) -> String {
  "\{parent}[\{index}]"
}

///|
fn json_field_path(parent : String, key : String) -> String {
  "\{parent}.\{key}"
}

///|
fn string_has(text : String, needle : String) -> Bool {
  text.find(needle) != None
}

///|
fn string_is_box_name(name : String) -> Bool {
  string_has(name, "box") ||
  string_has(name, "Box") ||
  string_has(name, "hit") ||
  string_has(name, "Hit") ||
  string_has(name, "hurt") ||
  string_has(name, "Hurt") ||
  string_has(name, "collision") ||
  string_has(name, "Collision")
}

///|
fn json_escape(text : String) -> String {
  let mut out = ""
  for i in 0.. String {
  "\"" + json_escape(text) + "\""
}

///|
fn join_strings(items : Array[String], sep : String) -> String {
  if items.length() == 0 {
    ""
  } else {
    for i in 0..