Skip to content

fix(httpgen): wrapper response messages missing transitive MarshalJSON for int64_encoding=NUMBER #124

@hasanMshawrab

Description

@hasanMshawrab

Bug Description

When a proto message with int64_encoding=NUMBER fields is nested inside a response wrapper message, the generated server handler serializes the numbers as strings instead of JSON numbers.

Root Cause

marshalResponse (in *_http_binding.pb.go) checks if the response implements json.Marshaler before deciding to use json.Marshal vs protojson.Marshal:

if marshaler, ok := response.(json.Marshaler); ok {
    return marshaler.MarshalJSON()   // dispatches to custom method
}
return protojson.Marshal(msg)        // bypasses MarshalJSON entirely

protojson.Marshal uses protobuf reflection — it does NOT call Go interface methods like json.Marshaler. So even though the inner message has a custom MarshalJSON() generated, it is never called.

The generator (encoding.go:collectInt64EncodingMessages) only generates MarshalJSON() for messages that directly contain NUMBER-encoded int64 fields. Wrapper messages that merely reference such messages are not detected.

Steps to Reproduce

message EventEntry {
  repeated int64 timestamps = 1 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
}

// Wrapper — no direct int64 NUMBER fields
message GetEventsResponse {
  EventEntry data = 1;
}

service EventService {
  rpc GetEvents(GetEventsRequest) returns (GetEventsResponse) { ... }
}
  1. Generate server with protoc-gen-go-http
  2. Implement handler returning GetEventsResponse with timestamps populated
  3. Call the endpoint
  4. Expected: {"data": {"timestamps": [1700000000000, 1700000001000]}}
  5. Actual: {"data": {"timestamps": ["1700000000000", "1700000001000"]}}

What Should Happen

GetEventsResponse should also receive a generated MarshalJSON() that re-marshals its data field via json.Marshal(x.Data) (which dispatches to EventEntry.MarshalJSON()).

Affected Components

  • internal/httpgen/encoding.gocollectInt64EncodingMessages is not transitive
  • internal/clientgen/encoding.go — identical gap

Fix

Two-phase collection in generateInt64EncodingFile:

  1. Existing: detect messages with direct NUMBER fields
  2. New: detect wrapper messages whose fields are of a type from phase 1
  3. Generate wrapper MarshalJSON()/UnmarshalJSON() that re-marshal nested fields via json.Marshal

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions