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

  1. Pass a pointer to a generated proto message (e.g. &pb.MyRequest{}) as the value.
  2. For JSON/plain bodies use the JSONPb marshaller instead.
  3. Verify the value implements proto.Message via a proto.Message type assertion before calling Unmarshal.
  4. 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

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


AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02). Data as JSON: /api/errors/ab269d2015c8c0f1. Report an issue: GitHub.