zoobzio December 26, 2025 Edit this page

Quickstart

Requirements

Go 1.24 or later.

Installation

go get github.com/zoobzio/cereal

Basic Codec Usage

For simple marshaling without transforms, use a codec directly:

package main

import (
    "fmt"

    "github.com/zoobzio/cereal/json"
)

type Message struct {
    ID   string `json:"id"`
    Text string `json:"text"`
}

func main() {
    codec := json.New()

    msg := Message{ID: "1", Text: "Hello"}

    // Marshal
    data, err := codec.Marshal(msg)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))
    // {"id":"1","text":"Hello"}

    // Unmarshal
    var result Message
    err = codec.Unmarshal(data, &result)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", result)
    // {ID:1 Text:Hello}
}

Processor with Boundary Transforms

For boundary-aware serialization with automatic field transforms:

package main

import (
    "context"
    "fmt"

    "github.com/zoobzio/cereal"
    "github.com/zoobzio/cereal/json"
)

// 1. Define your type with boundary tags
type User struct {
    ID       string `json:"id"`
    Email    string `json:"email" store.encrypt:"aes" load.decrypt:"aes" send.mask:"email"`
    Password string `json:"password" receive.hash:"sha256"`
    Token    string `json:"token" send.redact:"[REDACTED]"`
}

// 2. Implement Cloner[T]
func (u User) Clone() User { return u }

func main() {
    ctx := context.Background()

    // 3. Create processor
    proc, err := cereal.NewProcessor[User]()
    if err != nil {
        panic(err)
    }

    // 4. Configure encryption
    key := []byte("32-byte-key-for-aes-256-encrypt!")
    enc, err := cereal.AES(key)
    if err != nil {
        panic(err)
    }
    proc.SetEncryptor(cereal.EncryptAES, enc)

    // 5. Validate configuration (optional - runs automatically on first operation)
    if err := proc.Validate(); err != nil {
        panic(err)
    }

    // 6. Primary API: T -> T transforms

    // Receive: transform incoming data (hashes password)
    incoming := User{ID: "123", Email: "alice@example.com", Password: "secret", Token: "abc123"}
    user, err := proc.Receive(ctx, incoming)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Received - Password hashed: %s...\n", user.Password[:16])

    // Store: transform for storage (encrypts email)
    stored, err := proc.Store(ctx, user)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Stored - Email encrypted: %s\n", stored.Email)

    // Load: transform from storage (decrypts email)
    loaded, err := proc.Load(ctx, stored)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Loaded - Email decrypted: %s\n", loaded.Email)

    // Send: transform for external output (masks email, redacts token)
    sanitized, err := proc.Send(ctx, loaded)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Sent - Email: %s, Token: %s\n", sanitized.Email, sanitized.Token)

    // 7. Secondary API: codec-aware byte serialization (optional)

    proc.SetCodec(json.New())

    // Decode: bytes -> *T with Load transforms
    data := []byte(`{"id":"123","email":"encrypted-value","password":"hashed","token":"abc123"}`)
    decoded, err := proc.Read(ctx, data)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Decoded - Email: %s\n", decoded.Email)

    // Encode: *T -> bytes with Send transforms
    response, err := proc.Encode(ctx, &loaded)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Encoded: %s\n", response)
}

What's Happening

  1. receive.hash:"sha256" - Hashes password when receiving external input
  2. store.encrypt:"aes" - Encrypts email before storing to database
  3. load.decrypt:"aes" - Decrypts email when loading from database
  4. send.mask:"email" - Masks email (a***@example.com) in API responses
  5. send.redact:"[REDACTED]" - Replaces token entirely in API responses
  6. Clone() - Returns a copy so the original is never modified
  7. context.Context - Flows through for observability integration

Performance Note

The processor uses reflection to apply transforms. For most use cases this is negligible (microseconds per operation). For performance-critical paths, implement override interfaces (Encryptable, Hashable, Maskable, Redactable) to bypass reflection entirely. See Escape Hatches.

Next Steps