Default json Codec fails
What happened:
Default json Codec fails to unmarshal string-formatted int64 fields in application/json requests
In standard Protobuf JSON mapping (proto3 specification), int64 values are allowed and recommended to be serialized/unserialized as JSON strings (e.g., "id": "123" instead of "id": 123) to prevent precision loss in 64-bit JavaScript numbers.
In Kratos v3, when web clients (e.g., Axios, Vue, React, Postman) send HTTP POST requests with Content-Type: application/json, attempting to send "id": "123" for a Protobuf int64 field results in the following unmarshaling error:
body unmarshal json: cannot unmarshal string into Go struct field MyRequest.id of type int64Simply importing _ "github.com/go-kratos/kratos/v3/encoding/protojson" in main.go (as suggested in issue #3820) does NOT resolve this issue.
What you expected to happen:
How to reproduce it (as minimally and precisely as possible):
Anything else we need to know?:
Root Cause Analysis
protojsoncodec registers itself under the name"protojson":// github.com/go-kratos/kratos/v3/encoding/protojson const Name = "protojson"- When a client sends
Content-Type: application/json, Kratos HTTP Server resolves the codec name as"json"viaCodecForRequest(r, "Content-Type"). - As a result, Kratos falls back to the default Go standard library
encoding/jsoncodec (registered under"json"), which strictly rejects string-to-int64 unmarshaling for struct fields. The imported"protojson"codec is never invoked.
Solution / Recommended Workaround
Customizing http.RequestDecoder on the http.Server options ensures that any proto.Message request payload is unmarshaled using protojson.UnmarshalOptions (which natively supports string-formatted int64), regardless of whether the HTTP Content-Type is application/json or application/protojson.
Implementation
package encoder
import (
"encoding/json"
"io"
"net/http"
kerrors "github.com/go-kratos/kratos/v3/errors"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// CustomRequestDecoder handles both application/json and application/protojson smoothly,
// supporting string-formatted int64 fields (e.g., "id": "123") without forcing clients to change Content-Type.
func CustomRequestDecoder(r *http.Request, v any) error {
data, err := io.ReadAll(r.Body)
if err != nil {
return kerrors.BadRequest("BODY", err.Error())
}
if m, ok := v.(proto.Message); ok {
if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(data, m); err != nil {
return kerrors.BadRequest("BODY", err.Error())
}
return nil
}
if err := json.Unmarshal(data, v); err != nil {
return kerrors.BadRequest("BODY", err.Error())
}
return nil
}Server Configuration
srv := http.NewServer(
http.Middleware(...),
http.RequestDecoder(encoder.CustomRequestDecoder),
)Suggestion for Kratos Framework
Consider updating the default json codec in github.com/go-kratos/kratos/v3/encoding/json or the default HTTP request decoder to check if the target struct is proto.Message, and fallback to protojson unmarshaling rules for application/json content types.
Environment:
- Kratos version (use
kratos -v): v3.0.0 - Go version (use
go version): 1.25.11 - OS (e.g:
cat /etc/os-release): - Others:
Source: go-kratos/kratos