Skip to content

Serialization Package

Flexible, type-safe serialization/deserialization with validation support for GoDoxy configuration.

Overview

Purpose

This package provides robust YAML/JSON serialization with:

  • Case-insensitive field matching using FNV-1a hashing
  • Environment variable substitution (${VAR} syntax)
  • Field-level validation with go-playground/validator tags
  • Custom type conversion with pluggable format handlers

Primary Consumers

  • internal/config/ - Configuration file loading
  • internal/autocert/ - ACME provider configuration
  • internal/route/ - Route configuration

Non-goals

  • Binary serialization (MsgPack, etc.)
  • Schema evolution/migration
  • Partial deserialization (unknown fields error)

Stability

Internal package with stable public APIs. Exported functions are production-ready.

Public API

Core Types

go
// Intermediate representation during deserialization
type SerializedObject = map[string]any

Interfaces

go
// For custom map unmarshaling logic
type MapUnmarshaller interface {
    UnmarshalMap(m map[string]any) error
}

// For custom validation logic
type CustomValidator interface {
    Validate() error
}

Deserialization Functions

go
// Generic unmarshal with pluggable format handler
func UnmarshalValidate[T any](data []byte, target *T, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) error

// Read from io.Reader with format decoder
func UnmarshalValidateReader[T any](reader io.Reader, target *T, newDecoder newDecoderFunc, interceptFns ...interceptFunc) error

// Direct map deserialization
func MapUnmarshalValidate(src SerializedObject, dst any) error

// To xsync.Map with pluggable format handler
func UnmarshalValidateXSync[V any](data []byte, unmarshaler unmarshalFunc, interceptFns ...interceptFunc) (*xsync.Map[string, V], error)

File I/O Functions

go
// Write marshaled data to file
func SaveFile[T any](path string, src *T, perm os.FileMode, marshaler marshalFunc) error

// Read and unmarshal file if it exists
func LoadFileIfExist[T any](path string, dst *T, unmarshaler unmarshalFunc) error

Conversion Functions

go
// Convert any value to target reflect.Value
func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) error

// String to target type conversion
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr error)

Validation Functions

go
// Validate using struct tags
func ValidateWithFieldTags(s any) error

// Register custom validator
func MustRegisterValidation(tag string, fn validator.Func)

// Validate using CustomValidator interface
func ValidateWithCustomValidator(v reflect.Value) error

// Get underlying validator
func Validator() *validator.Validate

Utility Functions

go
// Register default value factory
func RegisterDefaultValueFactory[T any](factory func() *T)

// Convert map to SerializedObject
func ToSerializedObject[VT any](m map[string]VT) SerializedObject

Architecture

Data Flow

Component Interactions

Field Tag Reference

TagPurposeExample
jsonField name for serializationjson:"auth_token"
deserializeExclude field from deserializationdeserialize:"-"
validatego-playground/validator tagsvalidate:"required,email"
aliasesAlternative field namesaliases:"key,api_key"

Configuration Surface

Supported Field Types

  • Primitives (string, int, bool, float)
  • Pointers to primitives
  • Slices of primitives
  • Maps with string keys
  • Nested structs
  • Time.Duration (with extended units: d, w, M)

Environment Variable Substitution

yaml
autocert:
  auth_token: ${CLOUDFLARE_AUTH_TOKEN}
  # Lookup order: GODOXY_VAR, GOPROXY_VAR, VAR

String Conversion Formats

TypeFormat Examples
Duration1h30m, 2d, 1w, 3M
Numeric123, 0xFF, -42
Slicea,b,c or YAML list format
Map/StructYAML key: value format

Dependency and Integration Map

External Dependencies

  • github.com/goccy/go-yaml - YAML parsing
  • github.com/go-playground/validator/v10 - Validation
  • github.com/puzpuzpuz/xsync/v4 - Type cache
  • github.com/bytedance/sonic - JSON operations

Internal Dependencies

  • github.com/yusing/goutils/errs - Error handling
  • github.com/yusing/gointernals - Reflection utilities

Observability

Errors

All errors use gperr with structured subjects:

go
ErrUnknownField.Subject("field_name").With(gperr.DoYouMeanField("field_name", ["fieldName"]))
ErrValidationError.Subject("Namespace").Withf("required")
ErrUnsupportedConversion.Subjectf("string to int")

Performance Characteristics

OperationComplexityNotes
Type info lookupO(1)Cached in xsync.Map
Field matchingO(1)FNV-1a hash lookup
ConversionO(n)n = number of fields
ValidationO(n)n = number of validatable fields

Failure Modes and Recovery

Failure ModeResultRecovery
Unknown fieldError with suggestionsFix config field name
Validation failureStructured errorFix field value
Type mismatchErrorCheck field type
Missing env varErrorSet environment variable
Invalid formatErrorFix YAML/JSON syntax

Usage Examples

YAML Deserialization

go
type ServerConfig struct {
    Host        string `json:"host" validate:"required,hostname_port"`
    Port        int    `json:"port" validate:"required,min=1,max=65535"`
    MaxConns    int    `json:"max_conns"`
    TLSEnabled  bool   `json:"tls_enabled"`
}

yamlData := []byte(`
host: localhost
port: 8080
max_conns: 100
tls_enabled: true
`)

var config ServerConfig
if err := serialization.UnmarshalValidate(yamlData, &config, yaml.Unmarshal); err != nil {
    panic(err)
}

JSON Deserialization

go
var config ServerConfig
if err := serialization.UnmarshalValidate(jsonData, &config, json.Unmarshal); err != nil {
    panic(err)
}

Custom Validator

go
type Config struct {
    URL string `json:"url" validate:"required"`
}

func (c *Config) Validate() error {
    if !strings.HasPrefix(c.URL, "https://") {
        return errors.New("url must use https")
    }
    return nil
}

Custom Type with Parser Interface

go
type Duration struct {
    Value int
    Unit  string
}

func (d *Duration) Parse(v string) error {
    // custom parsing logic
    return nil
}

Reading from File

go
var config ServerConfig
if err := serialization.LoadFileIfExist("config.yml", &config, yaml.Unmarshal); err != nil {
    panic(err)
}

// Save back to file
if err := serialization.SaveFile("config.yml", &config, 0644, yaml.Marshal); err != nil {
    panic(err)
}

Reading from io.Reader

go
var config ServerConfig
file, _ := os.Open("config.yml")
defer file.Close()
if err := serialization.UnmarshalValidateReader(file, &config, yaml.NewDecoder); err != nil {
    panic(err)
}

Testing Notes

  • serialization_test.go - Core functionality tests
  • validation_*_test.go - Tag validation tests
  • Golden files for complex configurations
  • Tests cover:
    • Case-insensitive field matching
    • Anonymous struct handling
    • Pointer primitives
    • String conversions
    • Environment substitution
    • Custom validators
    • Multiple format handlers (YAML/JSON)

Released under the MIT License.