Encoding


Overview

goa supports a flexible encoding and decoding strategy that makes it possible to associate arbitrary encoders and decoders with given response and request content types. By default all goa services can decode and encode JSON, XML and gob.

Decoding

The goa decoder looks at the incoming request Content-Type header and matches it with a decoder. By default application/json is mapped to the JSON decoder, application/xml to the XML decoder and application/gob to the gob decoder. The JSON decoder is also used when the Content-Type header is missing or does not match one of the known values. If decoding fails goa writes an error response using status code 400 using the ErrInvalidEncoding error to write the body (see Error Handling for more information on how errors translate to HTTP responses).

Encoding

The goa encoder looks at the Accept header of the incoming request and implements a simple content negotiation algorithm to match it with the encoders available to the service. Similarly to the decoder it supports JSON, XML and gob by default. It also defaults to JSON if there is no Accept header or if its value does not match one of the known content types.

Using Custom Decoders

There are many reasons why your service may need to use different decoders. For example you may want to switch from the stdlib JSON package to a custom package that may provide a performance improvement for your use case. You may also need to support different serialization formats such as msgpack.

goa defines the interface that a decoder must implement to be mounted on a goa service. The interface is:

// A Decoder unmarshals an io.Reader into an interface.
Decoder interface {
	Decode(v interface{}) error
}

A decoder may also implement the resettable decoder interface which adds a Reset method:

// ResettableDecoder is used to determine whether or not a Decoder can be reset and thus
// safely reused in a sync.Pool.
ResettableDecoder interface {
	Decoder
	Reset(r io.Reader)
}

A decoder that implements ResettableDecoder makes it possible for goa to maintain a pool of reusable instances thereby significantly reducing the overhead of creating decoders for each request.

goa also defines the function signature for a decoder constructor that it uses to create instances of the decoder:

// DecoderFunc instantiates a decoder that decodes data read from the given io reader.
DecoderFunc func(r io.Reader) Decoder

Packages that implement custom decoders must expose a NewDecoder function (by default, see below) that implements DecoderFunc. The code generated by goa automatically calls that function to create new decoder to decode incoming request payloads.

Setting Up Custom Decoders

Using a custom decoder is a simple two step process:

  1. The design of the service must use the Consumes DSL function to list the content types and corresponding decoder packages and optionally function.

  2. The package implementing the DecoderFunc function describes above must be imported by the service.

goagen app takes care of generating the code that configures the service decoders (in the controllers.go file if you’re curious).

The Consumes DSL allows overriding the name of the package function used to create decoders via the Function DSL:

    Consumes("application/json", func() {
        Package("github.com/myjsondecoder")
        Function("CustomNewDecoder")
    })

Setting the Default Decoder

The default decoder is the decoder used when the incoming request Content-Type header is missing or does not match any of the content types defined in the Consumes DSL. It is the first decoder to be listed in the Consumes DSL. If no decoder is listed then the generated code defaults to the stdlib JSON decoder.

Built-in Custom Decoders

goa comes with a few custom decoders (or to be more exact adapters to external decoders) that cover binc, cbor, msgpack and ugorji’s JSON decoder. Taking msgpack as an example, the design would look like:

var _ = API("My API", func() {
    // ...
    Consumes("application/msgpack", func() {
        Package("github.com/goadesign/goa/encoding/msgpack")
    })
    // ...
})

And that’s all there is to do. The goa msgpack package exposes the NewDecoder function and the generated code takes care of configuring the goa decoder.

Using Custom Encoders

The mechanism for using custom encoders is very similar to using custom decoders. An encoder must implement the Encoder interface:

// An Encoder marshals from an interface into an io.Writer.
Encoder interface {
	Encode(v interface{}) error
}

It may also optionally implement the ResettableEncoder interface thereby enabling the use of sync pools:

// The ResettableEncoder is used to determine whether or not a Encoder can be reset and
// thus safely reused in a sync.Pool.
ResettableEncoder interface {
	Encoder
	Reset(w io.Writer)
}

The encoder package must implement a NewEncoder function with the following signature:

// EncoderFunc instantiates an encoder that encodes data into the given writer.
EncoderFunc func(w io.Writer) Encoder

Setting Up Custom Encoders

The DSL function for specifying custom encoders is Produces, it supports the same syntax as Consumes:

var _ = API("My API", func() {
    // ...
    Produces("application/msgpack", func() {
        Package("github.com/goadesign/goa/encoding/msgpack")
    })
    // ...
})

As with decoders the first encoder listed in the DSL becomes the default encoder used when the incoming request Accept header is missing or does not match any of the content types listed in the design. If no encoder is listed in the design then the generated code defaults to the stdlib JSON encoder.