grpc-ecosystem/grpc-gateway · error
unable to unmarshal non proto field
Error message
unable to unmarshal non proto field
What it means
ProtoMarshaller.Unmarshal requires the destination value to implement proto.Message. If the caller passes any other type (a plain struct, map, interface pointing elsewhere), it returns "unable to unmarshal non proto field" instead of attempting proto.Unmarshal, which would panic or fail. This guards the proto decoding path used for application/proto request bodies.
Source
Thrown at runtime/marshal_proto.go:31
// ContentType always returns "application/octet-stream".
func (*ProtoMarshaller) ContentType(_ interface{}) string {
return "application/octet-stream"
}
// Marshal marshals "value" into Proto
func (*ProtoMarshaller) Marshal(value interface{}) ([]byte, error) {
message, ok := value.(proto.Message)
if !ok {
return nil, errors.New("unable to marshal non proto field")
}
return proto.Marshal(message)
}
// Unmarshal unmarshals proto "data" into "value"
func (*ProtoMarshaller) Unmarshal(data []byte, value interface{}) error {
message, ok := value.(proto.Message)
if !ok {
return errors.New("unable to unmarshal non proto field")
}
return proto.Unmarshal(data, message)
}
// NewDecoder returns a Decoder which reads proto stream from "reader".
func (marshaller *ProtoMarshaller) NewDecoder(reader io.Reader) Decoder {
return DecoderFunc(func(value interface{}) error {
buffer, err := io.ReadAll(reader)
if err != nil {
return err
}
return marshaller.Unmarshal(buffer, value)
})
}
// NewEncoder returns an Encoder which writes proto stream into "writer".
func (marshaller *ProtoMarshaller) NewEncoder(writer io.Writer) Encoder {
return EncoderFunc(func(value interface{}) error {View on GitHub (pinned to a58a4436a3)
Solutions
- Pass a pointer to a generated proto message (e.g. &pb.MyRequest{}) as the value.
- For JSON/plain bodies use the JSONPb marshaller instead.
- Verify the value implements proto.Message via a proto.Message type assertion before calling Unmarshal.
- Fix middleware/handler code that decodes with the wrong marshaller for the content type.
Example fix
// before
var req map[string]interface{}
err := protoMarshaller.Unmarshal(data, &req)
// after
req := &pb.MyRequest{}
err := protoMarshaller.Unmarshal(data, req) Defensive patterns
Strategy: type-guard
Validate before calling
if _, ok := value.(proto.Message); !ok {
return fmt.Errorf("cannot unmarshal into %T; destination must implement proto.Message", value)
} Type guard
func canUnmarshalProto(value interface{}) bool {
_, ok := value.(proto.Message)
return ok
} Try / catch
err := marshaller.Unmarshal(data, value)
if err != nil {
if err.Error() == "unable to unmarshal non proto field" {
return fmt.Errorf("destination must be a proto message, got %T", value)
}
return err
} Prevention
- Decode requests into pointers to generated proto request types only.
- Use JSONPb (or runtime.JSONPb{}) for non-proto request bodies.
- Type-assert to proto.Message in middleware before delegating to the proto marshaller.
When it happens
Trigger: Calling ProtoMarshaller.Unmarshal(data, value) where value is not a proto.Message — e.g. decoding a request into a custom struct, a map, or a double pointer that does not satisfy the proto.Message interface.
Common situations: Custom HTTP handlers reusing the proto marshaller to decode non-proto bodies; writing middleware that decodes into generic structs; tests (like TestProtoMarshalUnmarshal) passing wrong argument types by mistake; switching a route from JSON to proto content type without changing the decode target.
Related errors
- unable to marshal non proto field
- failed to unmarshal code generator request: %w
- no target service defined in the file
- only primitive and enum types are allowed in repeated path p
- only primitive and well-known types are allowed in path para
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/ab269d2015c8c0f1.
Report an issue: GitHub.