///|
/// Prepare an envelope's JSON for decoding.
///
/// - Object members whose value is `null` are removed, so an explicit `null`
///   on an optional field decodes as `None` in every message type. A `null`
///   on a required field then reports the field as missing.
/// - Every number in the protocol schema is an integer, so a number with a
///   fractional part raises an error instead of being truncated.
fn normalize_envelope_json(
  json : Json,
  path : @json.JsonPath,
) -> Json raise @json.JsonDecodeError {
  match json {
    Object(obj) => {
      let out : Map[String, Json] = Map([], capacity=obj.length())
      for key, value in obj {
        if value is Null {
          continue
        }
        out[key] = normalize_envelope_json(value, path.add_key(key))
      }
      Json::object(out)
    }
    Array(items) => {
      let out : Array[Json] = Array(capacity=items.length())
      for i, item in items {
        out.push(normalize_envelope_json(item, path.add_index(i)))
      }
      Json::array(out)
    }
    Number(n, ..) if n != n.floor() =>
      raise @json.JsonDecodeError((path, "expected an integer, got \{n}"))
    _ => json
  }
}