How to Implement Custom Codec Serializers in Kratos for Non-Standard Formats
You can implement custom codec serializers in Kratos by creating a type that satisfies the encoding.Codec interface and registering it with encoding.RegisterCodec, enabling automatic content negotiation for any non-standard media type such as application/vnd.myapp+protobuf or application/custom.
The go-kratos/kratos framework abstracts serialization behind a pluggable codec system defined in encoding/encoding.go. By implementing custom codec serializers, you can transparently handle proprietary binary formats, specialized JSON variants, or legacy wire protocols without modifying core transport logic in the HTTP or gRPC layers.
Understanding the Codec Interface and Registry
The foundation of Kratos serialization is the encoding.Codec interface. This contract requires three methods: Marshal(v any) ([]byte, error) for serialization, Unmarshal(data []byte, v any) error for deserialization, and Name() string which returns the content subtype (e.g., "json", "protobuf", "custom").
The global registry in encoding/encoding.go stores codec instances in a map keyed by the lower-cased result of Name(). The RegisterCodec function validates the codec and adds it to registeredCodecs, making it available to all transports.
How Kratos Looks Up Custom Codecs
Kratos transports automatically select codecs based on HTTP headers or gRPC content types. The lookup mechanism extracts the subtype from headers and queries the registry.
Request Decoding via CodecForRequest
In transport/http/codec.go, the CodecForRequest function scans the Content-Type header, extracts the subtype using httputil.ContentSubtype, and calls encoding.GetCodec to obtain the matching codec. If none is found, it falls back to the built-in JSON codec.
// transport/http/codec.go
func CodecForRequest(r *http.Request, name string) (encoding.Codec, bool) {
for _, accept := range r.Header[name] {
codec := encoding.GetCodec(httputil.ContentSubtype(accept))
if codec != nil {
return codec, true
}
}
return encoding.GetCodec("json"), false
}
Response Encoding via CodecForResponse
The CodecForResponse function performs the same lookup against the Content-Type or Accept headers to determine the appropriate encoder for responses.
// transport/http/codec.go
func CodecForResponse(r *http.Response) encoding.Codec {
codec := encoding.GetCodec(httputil.ContentSubtype(r.Header.Get("Content-Type")))
if codec != nil {
return codec
}
return encoding.GetCodec("json")
}
Codec Registration Mechanism
The central registry implementation in encoding/encoding.go handles the mapping:
// encoding/encoding.go
var registeredCodecs = make(map[string]Codec)
func RegisterCodec(codec Codec) {
if codec == nil {
panic("cannot register a nil Codec")
}
if codec.Name() == "" {
panic("cannot register Codec with empty string result for Name()")
}
contentSubtype := strings.ToLower(codec.Name())
registeredCodecs[contentSubtype] = codec
}
Step-by-Step: Creating a Custom Codec Serializer
Follow these steps to integrate a non-standard format into your Kratos application:
- Implement the Codec Interface – Create a type satisfying
encoding.CodecwithMarshal,Unmarshal, andNamemethods. - Register the Codec – Call
encoding.RegisterCodecin aninitfunction or during application bootstrap. - Configure Content Negotiation – Set the appropriate
Content-TypeorAcceptheaders to trigger automatic codec selection. - Extend to gRPC (Optional) – For gRPC transports, additionally register with
google.golang.org/grpc/encoding.
Complete Implementation Example
Below is a minimal custom codec that handles a fictional application/custom format.
// custom_codec.go
package mycodec
import (
"errors"
"github.com/go-kratos/kratos/v2/encoding"
)
// customCodec implements the encoding.Codec interface.
type customCodec struct{}
// Marshal converts a Go value into the custom wire format.
func (customCodec) Marshal(v any) ([]byte, error) {
s, ok := v.(string)
if !ok {
return nil, errors.New("customCodec only supports string payloads")
}
// prepend a marker to identify the format
return []byte("CUST:" + s), nil
}
// Unmarshal parses the custom wire format back into a Go value.
func (customCodec) Unmarshal(data []byte, v any) error {
ptr, ok := v.(*string)
if !ok {
return errors.New("customCodec only supports *string destination")
}
// strip the marker
if len(data) < 5 || string(data[:5]) != "CUST:" {
return errors.New("invalid custom payload")
}
*ptr = string(data[5:])
return nil
}
// Name returns the media subtype used in HTTP headers.
func (customCodec) Name() string { return "custom" }
// init registers the codec when the package is imported.
func init() {
encoding.RegisterCodec(customCodec{})
}
Using the Codec in HTTP Handlers
Once registered, the codec activates automatically based on headers:
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Expect the client to send Content-Type: application/custom
var payload string
if err := http.DefaultRequestDecoder(r, &payload); err != nil {
http.DefaultErrorEncoder(w, r, err)
return
}
// Echo back using the same format (Accept header influences response)
resp := "received:" + payload
if err := http.DefaultResponseEncoder(w, r, resp); err != nil {
http.DefaultErrorEncoder(w, r, err)
}
}
When the client sends:
POST /my-endpoint HTTP/1.1
Content-Type: application/custom
Accept: application/custom
CUST:hello
the server decodes via customCodec.Unmarshal and encodes replies via customCodec.Marshal.
Registering for gRPC Transport
For gRPC, register the codec with both Kratos and the standard gRPC encoding package:
// grpc_custom.go
package mygrpc
import (
"google.golang.org/grpc/encoding"
kratosencoding "github.com/go-kratos/kratos/v2/encoding"
"myproject/mycodec"
)
// init registers the codec for both transports.
func init() {
// Kratos registration (already performed by mycodec init)
// Register with gRPC using the same name.
encoding.RegisterCodec(&grpcCodec{})
}
type grpcCodec struct{}
func (grpcCodec) Name() string { return mycodec.customCodec{}.Name() }
func (grpcCodec) Marshal(v any) ([]byte, error) {
return mycodec.customCodec{}.Marshal(v)
}
func (grpcCodec) Unmarshal(data []byte, v any) error {
return mycodec.customCodec{}.Unmarshal(data, v)
}
Now gRPC services can exchange the custom format via the same subtype (custom).
Key Source Files in go-kratos/kratos
| File | Purpose |
|---|---|
[encoding/encoding.go](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) |
Defines Codec interface and the global registry (RegisterCodec, GetCodec). |
[transport/http/codec.go](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go) |
Shows how HTTP transports query the registry based on Content-Type / Accept. |
[transport/http/codec_test.go](https://github.com/go-kratos/kratos/blob/main/transport/http/codec_test.go) |
Demonstrates registration of a mock codec and the effect on encoding/decoding. |
[transport/grpc/codec.go](https://github.com/go-kratos/kratos/blob/main/transport/grpc/codec.go) |
Shows integration of Kratos codecs with the gRPC encoding subsystem. |
Summary
- Custom codec serializers in Kratos implement the
encoding.Codecinterface withMarshal,Unmarshal, andNamemethods. - Register implementations via
encoding.RegisterCodecinencoding/encoding.goto make them available globally. - HTTP transports in
transport/http/codec.goautomatically select codecs by inspectingContent-TypeandAcceptheaders usinghttputil.ContentSubtype. - For gRPC support, additionally register with
google.golang.org/grpc/encodingto bridge Kratos codecs with the gRPC transport. - This architecture enables transparent handling of non-standard formats without modifying core framework code.
Frequently Asked Questions
What is the Codec interface in Kratos?
The Codec interface is defined in encoding/encoding.go and requires three methods: Marshal(v any) ([]byte, error) for serialization, Unmarshal(data []byte, v any) error for deserialization, and Name() string which returns the content subtype (e.g., "json", "custom"). All custom serializers must satisfy this contract to integrate with Kratos transports.
How does Kratos determine which codec to use for an HTTP request?
Kratos determines the codec by examining the Content-Type header for requests and the Accept header for responses. In transport/http/codec.go, the CodecForRequest function extracts the subtype using httputil.ContentSubtype and queries the registry via encoding.GetCodec. If no registered codec matches, it falls back to the built-in JSON codec.
Can the same custom codec be used for both HTTP and gRPC transports?
Yes, but gRPC requires additional registration. While Kratos uses its own registry in encoding/encoding.go, the gRPC transport relies on the standard google.golang.org/grpc/encoding package. You must create a wrapper that implements both interfaces and register it with both encoding.RegisterCodec (for Kratos) and grpc.encoding.RegisterCodec (for gRPC) to ensure the custom format works across all transports.
What happens if no codec is found for the requested content type?
If encoding.GetCodec returns nil because no registered codec matches the extracted subtype, the HTTP transport in transport/http/codec.go falls back to the built-in JSON codec. This ensures the server can always respond, but it may result in encoding errors if the client expects a different format. Always verify that your custom codec is properly registered and the content type headers are correctly set to avoid unexpected fallback behavior.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →