JSON cache encoder
Generic JSON encoder implementing cache.EncoderPort[V] for any value type. It registers with a cache.EncodingRegistry to handle the serialisation step on every cache write and read.
Overview
The JSON encoder turns Go values into UTF-8 JSON bytes. JSON is portable across languages and tooling. A Python worker can read a JSON-encoded cache entry written by a Go service, and you can inspect cached values with redis-cli GET key | jq. The trade-off against Gob is that JSON output is larger and lossy for some Go types. JSON preserves field names as text, time.Time round-trips as a string, and []byte becomes base64.
Reach for JSON when the cache crosses language boundaries or when human-readable diagnostics matter. Reach for Gob when the cache is Go-only and payload size dominates.
The encoder does not call encoding/json directly. It marshals through piko's pluggable JSON layer, a frozen configuration resolved against the active JSON provider. The stdlib provider is the default. Activating the Sonic provider during bootstrap swaps the implementation for a faster one. The frozen configuration resolves lazily on first use, so an encoder created in init() picks up Sonic if it activates later in bootstrap.
The frozen configuration sets EscapeHTML to false, and both the stdlib provider and Sonic honour it. The encoder leaves <, >, and & unescaped, which differs from raw encoding/json output. Two further fields, CopyString and UseInt64, take effect only under Sonic. CopyString copies decoded strings instead of pointing into the source buffer. This keeps a decoded value safe to retain after the provider reuses the JSON buffer.
The encoder is generic over a single value type V. The pattern cache_encoder_json.New[any]() makes a default fallback encoder. Every type without a more specific encoder falls through to it. There is no Config struct and no defaults to learn. New[V]() returns a ready cache.EncoderPort[V].
Configuration
cache.NewEncodingRegistry and Register both take the type-agnostic cache.AnyEncoder interface. New[V]() returns the typed cache.EncoderPort[V], so assert it to cache.AnyEncoder when you pass it in.
import (
"context"
"piko.sh/piko/wdk/cache"
"piko.sh/piko/wdk/cache/cache_encoder_json"
)
func buildRegistry(ctx context.Context) (*cache.EncodingRegistry, error) {
// Type-specific encoder.
userEncoder := cache_encoder_json.New[User]()
// Universal fallback encoder for any value.
anyEncoder := cache_encoder_json.New[any]()
// Build the registry with the universal encoder as the default.
registry := cache.NewEncodingRegistry(anyEncoder.(cache.AnyEncoder))
if err := registry.Register(ctx, userEncoder.(cache.AnyEncoder)); err != nil {
return nil, err
}
return registry, nil
}
Register returns an error when the encoder is nil or when an encoder for the same type is already registered. Encoding runs before any transformer. The encoder produces bytes, then the crypto and zstd transformers act on those bytes. On read, the transformer chain reverses first, then the encoder decodes.
Pass the registry to a cache provider via its Registry field (Redis, Valkey, and so on). For a single namespace, attach a typed encoder to a cache builder with .TypedEncoder(encoder), which takes the cache.EncoderPort[V] directly.
Bootstrap
Encoders attach to the cache through the provider's Registry field. There is no piko.With* option for them.
import (
"piko.sh/piko/wdk/cache"
"piko.sh/piko/wdk/cache/cache_encoder_json"
"piko.sh/piko/wdk/cache/cache_provider_redis"
)
registry := cache.NewEncodingRegistry(cache_encoder_json.New[any]().(cache.AnyEncoder))
provider, err := cache_provider_redis.NewRedisProvider(cache_provider_redis.Config{
Address: "localhost:6379",
Registry: registry,
})
The encoder targets the provider-agnostic cache.EncoderPort[V]. The same registry works across every cache provider, so you build it once and reuse it for Redis, Valkey, and the cluster variants.
See also
Other cache encoders and transformers:
- Gob encoder, Go-native binary alternative.
- Crypto transformer, encrypt cached values after encoding.
- Zstd transformer, compress cached values after encoding.
JSON provider:
- Sonic, faster JSON provider this encoder picks up when activated.
Cache providers that consume the encoder registry:
- Redis, requires the
Registryfield. - Valkey, requires the
Registryfield. - Redis Cluster, requires the
Registryfield. - Valkey Cluster, requires the
Registryfield.
Framework docs:
- How to use the cache, wiring the cache service end-to-end.
- Cache API reference,
EncoderPort,EncodingRegistry, and the cache builder.