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.