go-kratos/kratos · error

failed to marshal, message is %T, want proto.Message

Error message

failed to marshal, message is %T, want proto.Message

What it means

Codec error from Kratos' protojson encoding.RegisterCodec: Marshal was called with a value that does not implement proto.Message (google.golang.org/protobuf/proto). The codec is registered under the protojson name, so any framework path that marshals with it - HTTP response encoding, codec.Marshal, mid-client calls - requires a real protobuf-generated message, not a plain Go struct, map, or string. %T reports the concrete type that was passed.

Source

Thrown at encoding/protojson/protojson.go:36

		EmitUnpopulated: true,
	}
	// UnmarshalOptions is a configurable JSON format parser.
	UnmarshalOptions = protojson.UnmarshalOptions{
		DiscardUnknown: true,
	}
)

func init() {
	encoding.RegisterCodec(codec{})
}

// codec is a Codec implementation with protojson.
type codec struct{}

func (codec) Marshal(v any) ([]byte, error) {
	m, ok := v.(proto.Message)
	if !ok {
		return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v)
	}
	return MarshalOptions.Marshal(m)
}

func (codec) Unmarshal(data []byte, v any) error {
	if len(data) == 0 {
		return nil
	}
	m, ok := v.(proto.Message)
	if !ok {
		return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
	}
	return UnmarshalOptions.Unmarshal(data, m)
}

func (codec) Name() string {
	return Name
}

View on GitHub (pinned to 668db92c2c)

Solutions

  1. Return the protobuf-generated message type itself (v2 API, google.golang.org/protobuf) from handlers/marshal calls
  2. Remove non-proto envelope wrappers; put metadata into proto fields instead of wrapping the message in an anonymous struct
  3. If you intended plain JSON, register/use the 'json' codec for that route instead of 'protojson'
  4. For legacy gogo/v1 generated code, regenerate with the modern protoc-gen-go or provide a custom codec that accepts those types
  5. Check the %T in the message to find which call site passed the wrong type

Example fix

// before: handler returns a plain struct under the protojson codec
type resp struct{ Msg string }
return &resp{Msg: "hi"} // -> failed to marshal, message is *main.resp, want proto.Message

// after: return the generated proto message
return &v1.SayHelloResponse{Msg: "hi"}, nil
Defensive patterns

Strategy: type-guard

Validate before calling

// Guard before marshaling with the protojson codec
func marshalProtojson(v any) ([]byte, error) {
	if _, ok := v.(proto.Message); !ok {
		return nil, fmt.Errorf("protojson codec requires proto.Message, got %T", v)
	}
	return protojson.Marshal(v.(proto.Message))
}

Type guard

func isProtoMessage(v any) bool {
	_, ok := v.(proto.Message)
	return ok
}

Try / catch

data, err := codec.Marshal(v)
if err != nil {
	if strings.Contains(err.Error(), "want proto.Message") {
		// wrong type at call site: return a developer-facing 500, never retry
		log.Error("protojson misuse", "type", fmt.Sprintf("%T", v))
		return errors.InternalServer("CODEC_MISUSE", err.Error())
	}
}

Prevention

When it happens

Trigger: Returning a non-proto type (plain struct, map[string]any, string) from a Kratos HTTP handler whose response codec is protojson; passing a gogo/protobuf or golang/protobuf (v1 API) message, which does not implement the new proto.Message; configuring the route/service to use codec name 'protojson' while the handler returns JSON-built structs; client-side codec.Marshal(protojson.Name) over arbitrary values.

Common situations: Mixing JSON-first handlers into a proto-based service; migrating from gogo/golang-protobuf generated types to google.golang.org/protobuf and missing one message; wrapping proto messages in an envelope struct (e.g. {Data: msg}) that itself is not a proto message; using protojson codec by name string where a json codec was intended.

Related errors


AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16). Data as JSON: /api/errors/a7858d33fb0ecc10. Report an issue: GitHub.