Skip to content
Zever
Home

Codec

Serialize typed values to bytes with composable Encoder and Decoder pairs, JSON by default.

2 min readEdit on GitHub

codec turns a typed Go value into bytes and back, so cache, queue, session, and webhook code never hand-roll json.Marshal. Use it with typed wrappers over byte stores, or when defining a wire format for a new adapter. Write a custom codec only when JSON is the wrong format.

codec has no YAML, no env, and no container accessor (there is no c.Codec()). Construct it directly:

main.go
package main
import (
"fmt"
"github.com/zenta-dev/zever/shared/codec"
)
type MyPayload struct {
ID string `json:"id"`
Name string `json:"name"`
}
func main() {
c := codec.JSONCodec[MyPayload]{}
data, _ := c.Encode(MyPayload{ID: "u_1", Name: "Ada"})
back, _ := c.Decode(data)
fmt.Printf("%s %s\n", back.ID, back.Name)
}
u_1 Ada

JSONCodec[V] is the only implementation Zever ships. Its output is deterministic, so snapshots and exact-byte assertions stay stable. A codec does no I/O and holds no connections, so it is safe to share. The interfaces are Encoder[V] (Encode), Decoder[V] (Decode), and Codec[V] (both); cache.NewTyped applies a Codec[V] to cache Get and Set.

Resolve the byte store from the container and pass the codec to the typed layer on top:

// imports: config, container, cachememory ("github.com/zenta-dev/zever/adapters/cache/memory"),
// "github.com/zenta-dev/zever/core/cache", "github.com/zenta-dev/zever/shared/codec"
cachememory.Register()
c := container.New(config.Default())
inner, _ := c.Cache()
typed := cache.NewTyped[string, MyPayload](inner, codec.JSONCodec[MyPayload]{})
typed.Set(ctx, "user:u_1", MyPayload{ID: "u_1", Name: "Ada"}, time.Minute)
got, _ := typed.Get(ctx, "user:u_1")
fmt.Println(got.Name)
Ada

Use one Codec[V] per stored type, shared by writer and reader. Queue and session paths follow the same shape: bytes on the wire, typed values in handlers. Resolve those with c.Queue() and c.Session().

Implement Codec[V] for a binary, compressed, or third-party format:

type gobCodec[V any] struct{}
func (gobCodec[V]) Encode(v V) ([]byte, error) { /* ... */ }
func (gobCodec[V]) Decode(data []byte) (V, error) { /* ... */ }
var _ codec.Codec[MyPayload] = gobCodec[MyPayload]{}

Keep it symmetric: whatever Encode writes, Decode must read back, including after a version adds a field. JSONCodec ignores unknown fields and defaults missing ones.

  • Never log encoded bytes at info level. Log IDs and sizes.
  • Keep payloads small: IDs and flags in sessions, not full records.
  • Use JSONCodec for any data you snapshot or hash.
Decode fails after adding a field

Added keys default fine, so the cause is a changed type. Keep JSON types stable or add a versioned wrapper type.

Cache reads return zero values

Writer and reader use different codecs. Build both sides from one Codec[V].

Snapshot tests flap

Use JSONCodec for snapshotted data, or sort keys in Encode.