///|
pub fn[T : @json.FromJson] opt(value : Json) -> T? {
  Some(@json.from_json(value)) catch {
    _ => None
  }
}

///|
pub fn[T : @json.FromJson] nul(value : Json) -> T?? {
  if value is Null {
    Some(None)
  } else {
    let parsed : T? = opt(value)
    if parsed is Some(v) {
      Some(Some(v))
    } else {
      None
    }
  }
}

///|
fn[U : JsonxPath] at_path(value : Json, path : U) -> Json? {
  let mut current = value
  for key in path.to_jsonx_path_iter() {
    guard current is Object(obj) else { return None }
    guard obj.get(key) is Some(next) else { return None }
    current = next
  }
  Some(current)
}

///|
pub fn[T : @json.FromJson, U : JsonxPath] get(value : Json, path : U) -> T? {
  guard at_path(value, path) is Some(current) else { return None }
  opt(current)
}

///|
pub fn sub(obj : Map[String, Json], key : String) -> Map[String, Json]? {
  if obj.get(key) is Some(Object(child)) {
    Some(child)
  } else {
    None
  }
}

///|
pub fn str(value : Json) -> String? {
  if value is String(v) {
    Some(v)
  } else if value is Number(v, ..) {
    Some(v.to_int().to_string())
  } else {
    None
  }
}

///|
pub fn[U : JsonxPath] strf(obj : Json, path : U) -> String? {
  guard at_path(obj, path) is Some(current) else { return None }
  str(current)
}

///|
pub fn int(value : Json) -> Int? {
  if value is Number(v, ..) {
    Some(v.to_int())
  } else if value is String(v) {
    (try? @string.parse_int(v)).to_option()
  } else {
    None
  }
}

///|
pub fn[U : JsonxPath] intf(obj : Json, path : U) -> Int? {
  guard at_path(obj, path) is Some(current) else { return None }
  int(current)
}