Hook System
Hooks allow inserting custom logic before and after JSON operations, enabling logging, performance monitoring, validation, and more.
Interface Signature Reference
For the complete type signatures of the Hook interface (Hook, HookContext, HookFunc), see Interface Definitions. This page focuses on usage guides and best practices.
Hook Interface
go
type Hook interface {
Before(ctx HookContext) error
After(ctx HookContext, result any, err error) (any, error)
}Method Description
| Method | Description |
|---|---|
Before(ctx HookContext) error | Called before the operation; returning an error aborts the operation |
After(ctx HookContext, result any, err error) (any, error) | Called after the operation; can modify the result or return an error |
HookContext Struct
HookContext provides context information about the operation.
go
type HookContext struct {
Operation string // Operation type: "get", "set", "delete", "marshal", "unmarshal"
JSONStr string // Input JSON string (may be empty during marshal). Security warning: may contain sensitive data
Path string // Target path (may be empty during marshal/unmarshal)
Value any // Value for set operations
Config *Config // Active configuration
StartTime time.Time // Operation start time
}Field Description
| Field | Type | Description |
|---|---|---|
Operation | string | Operation type, values: get, set, delete, marshal, unmarshal |
JSONStr | string | Input JSON string (Security warning: may contain sensitive data) |
Path | string | Target path expression |
Value | any | Value for set operations |
Config | *Config | Currently used configuration |
StartTime | time.Time | Operation start time |
HookFunc Adapter
HookFunc is a struct adapter that allows using functions as Hooks. Suitable for scenarios where only Before or After is needed.
go
type HookFunc struct {
BeforeFn func(ctx HookContext) error
AfterFn func(ctx HookContext, result any, err error) (any, error)
}Example
go
// Only need After
p.AddHook(&json.HookFunc{
AfterFn: func(ctx json.HookContext, result any, err error) (any, error) {
log.Printf("%s completed in %v", ctx.Operation, time.Since(ctx.StartTime))
return result, err
},
})
// Only need Before
p.AddHook(&json.HookFunc{
BeforeFn: func(ctx json.HookContext) error {
log.Printf("starting %s on path %s", ctx.Operation, ctx.Path)
return nil
},
})Convenience Hook Factory Functions
LoggingHook
Creates a logging Hook.
go
func LoggingHook(logger interface{ Info(msg string, args ...any) }) Hookgo
p.AddHook(json.LoggingHook(slog.Default()))TimingHook
Creates a timing Hook that records operation duration.
go
func TimingHook(recorder interface{ Record(op string, duration time.Duration) }) Hookgo
p.AddHook(json.TimingHook(myMetricsRecorder))ValidationHook
Creates a validation Hook that validates input before operations.
go
func ValidationHook(validator func(jsonStr, path string) error) Hookgo
p.AddHook(json.ValidationHook(func(jsonStr, path string) error {
if len(jsonStr) > 1_000_000 {
return errors.New("JSON too large")
}
return nil
}))ErrorHook
Creates an error handling Hook that intercepts and handles errors.
go
func ErrorHook(handler func(ctx HookContext, err error) error) Hookgo
p.AddHook(json.ErrorHook(func(ctx json.HookContext, err error) error {
sentry.CaptureException(err)
return err // Return original or transformed error
}))Custom Hook Implementation
Complete Example
go
package main
import (
"fmt"
"log/slog"
"time"
"github.com/cybergodev/json"
)
// Logging Hook
type LoggingHook struct {
logger *slog.Logger
}
func (h *LoggingHook) Before(ctx json.HookContext) error {
h.logger.Info("operation starting", "op", ctx.Operation, "path", ctx.Path)
return nil
}
func (h *LoggingHook) After(ctx json.HookContext, result any, err error) (any, error) {
h.logger.Info("operation completed",
"op", ctx.Operation,
"path", ctx.Path,
"duration", time.Since(ctx.StartTime),
"error", err)
return result, err
}
func main() {
cfg := json.DefaultConfig()
p, err := json.New(cfg)
if err != nil {
panic(err)
}
defer p.Close()
// Add custom Hook
p.AddHook(&LoggingHook{logger: slog.Default()})
// Use processor...
val, err := p.Get(`{"name": "test"}`, "name")
if err != nil {
panic(err)
}
fmt.Println(val)
}Simplified with HookFunc
go
// Only need to record completion time
p.AddHook(&json.HookFunc{
AfterFn: func(ctx json.HookContext, result any, err error) (any, error) {
fmt.Printf("%s took %v\n", ctx.Operation, time.Since(ctx.StartTime))
return result, err
},
})Configuring Hooks
Via Config
go
cfg := json.DefaultConfig()
cfg.Hooks = []json.Hook{
json.LoggingHook(slog.Default()),
json.TimingHook(myRecorder),
}
p, err := json.New(cfg)
if err != nil {
panic(err)
}Via Processor
go
p, err := json.New()
if err != nil {
panic(err)
}
p.AddHook(json.LoggingHook(slog.Default()))
p.AddHook(json.TimingHook(myRecorder))Execution Order
Before Hooks
- Executed in addition order
- Any Hook returning an error aborts the operation
After Hooks
- Executed in reverse addition order
- Every Hook executes (even if previous ones return errors)
go
// Addition order: A, B, C
p.AddHook(hookA)
p.AddHook(hookB)
p.AddHook(hookC)
// Execution order:
// Before: A.Before -> B.Before -> C.Before
// After: C.After -> B.After -> A.AfterBest Practices
1. Logging
go
p.AddHook(json.LoggingHook(slog.Default()))2. Performance Monitoring
go
type MetricsRecorder struct{}
func (m *MetricsRecorder) Record(op string, duration time.Duration) {
metrics.Histogram("json_operation_duration", duration, "op", op)
}
p.AddHook(json.TimingHook(&MetricsRecorder{}))3. Input Validation
go
p.AddHook(json.ValidationHook(func(jsonStr, path string) error {
if len(jsonStr) > 10*1024*1024 { // 10MB
return errors.New("JSON payload too large")
}
return nil
}))4. Error Tracking
go
p.AddHook(json.ErrorHook(func(ctx json.HookContext, err error) error {
if err != nil {
sentry.WithTags(map[string]string{
"operation": ctx.Operation,
"path": ctx.Path,
}).CaptureException(err)
}
return err
}))5. Audit Logging
go
type AuditHook struct {
auditLogger *slog.Logger
}
func (h *AuditHook) Before(ctx json.HookContext) error {
return nil
}
func (h *AuditHook) After(ctx json.HookContext, result any, err error) (any, error) {
if ctx.Operation == "set" || ctx.Operation == "delete" {
h.auditLogger.Info("data modification",
"operation", ctx.Operation,
"path", ctx.Path,
"success", err == nil)
}
return result, err
}See Also
- Interfaces - Extension interfaces
- Validator - Validators
- Config - Configuration options