///|
pub fn Id::to_json(self : Id) -> Json {
  {
    "client": Json::number(
      self.client.to_double(),
      repr=self.client.to_string(),
    ),
    "clock": Json::number(self.clock.to_double(), repr=self.clock.to_string()),
  }
}

///|
pub fn Id::from_json(value : Json) -> Id raise DecodeError {
  guard value is Object(fields) &&
    fields.get("client") is Some(client) &&
    fields.get("clock") is Some(clock) else {
    raise InvalidData("invalid item identity")
  }
  fn number(value : Json, maximum : UInt64) -> UInt64 raise DecodeError {
    guard value is Number(_, repr~) else {
      raise InvalidData("expected unsigned integer")
    }
    let repr = repr.unwrap_or(value.stringify())
    guard !repr.is_empty() else { raise InvalidData("empty integer") }
    let mut n = 0UL
    for ch in repr {
      guard ch >= '0' && ch <= '9' else {
        raise InvalidData("invalid unsigned integer")
      }
      let digit = (ch.to_int() - 48).to_uint64()
      guard n <= (maximum - digit) / 10UL else {
        raise InvalidData("integer overflow")
      }
      n = n * 10UL + digit
    }
    n
  }
  {
    client: number(client, 0xffff_ffff_ffff_ffffUL),
    clock: number(clock, 0xffff_ffffUL).to_uint(),
  }
}

///|
/// Rust serde representation of a logical branch identity.
pub fn BranchId::to_json(self : BranchId) -> Json {
  match self {
    Root(name) => { "Root": Json(name) }
    Nested(id) => { "Nested": id.to_json() }
  }
}

///|
pub fn BranchId::from_json(value : Json) -> BranchId raise DecodeError {
  guard value is Object(fields) && fields.length() == 1 else {
    raise InvalidData("invalid branch identity")
  }
  match fields.get("Root") {
    Some(String(name)) => Root(name)
    None =>
      match fields.get("Nested") {
        Some(value) => Nested(Id::from_json(value))
        None => raise InvalidData("invalid branch identity")
      }
    _ => raise InvalidData("invalid root identity")
  }
}

///|
pub fn[S] Hook::to_json(self : Hook[S]) -> Json {
  { "id": self.id().to_json(), "_tag": Json::null() }
}

///|
pub fn[S] Hook::from_json(value : Json) -> Hook[S] raise DecodeError {
  guard value is Object(fields) &&
    fields.get("id") is Some(id) &&
    fields.get("_tag") == Some(Json::null()) else {
    raise InvalidData("invalid hook")
  }
  Hook(BranchId::from_json(id))
}