Skip to main content

Mapx

The mapx package provides bidirectional conversion between Go structs and map[string]any, built on top of github.com/go-viper/mapstructure/v2. VEF overrides the upstream default tag — the framework uses json tags by default.

API Reference

APIContract
mapx.DecoderHookExported composed mapstructure.DecodeHookFunc used by default decoders
mapx.DecoderOptionFunction option type that mutates mapstructure.DecoderConfig
mapx.MetadataAlias for mapstructure.Metadata
mapx.NewDecoder(result, options...)Creates a mapstructure.Decoder with VEF defaults, then applies options in order
mapx.ToMap(value, options...)Converts a struct or pointer-to-struct into map[string]any; non-struct input returns ErrInvalidToMapValue
mapx.FromMap[T](value, options...)Converts map[string]any into *T; non-struct T returns ErrInvalidFromMapType
mapx.WithTagName(tagName)Sets DecoderConfig.TagName; default is json
mapx.WithIgnoreUntaggedFields(ignore)Sets DecoderConfig.IgnoreUntaggedFields to the supplied boolean
mapx.WithDecodeHook(decodeHook)Replaces DecoderConfig.DecodeHook; compose with mapx.DecoderHook yourself to preserve defaults
mapx.WithMatchName(matchName)Replaces the key/field matcher; the default is mapKey == lo.CamelCase(fieldName)
mapx.WithErrorUnused()Sets ErrorUnused = true
mapx.WithErrorUnset()Sets ErrorUnset = true
mapx.WithZeroFields()Sets ZeroFields = true
mapx.WithAllowUnsetPointer()Sets AllowUnsetPointer = true
mapx.WithMetadata(metadata)Stores decode metadata in the supplied *mapx.Metadata
mapx.WithWeaklyTypedInput()Sets WeaklyTypedInput = true
mapx.WithDecodeNil()Sets DecodeNil = true
mapx.ErrInvalidToMapValueSentinel for ToMap input that is not a struct or pointer-to-struct
mapx.ErrInvalidFromMapTypeSentinel for FromMap[T] when T is not a struct
mapx.ErrCollectionSetNilElementSentinel for nil elements while decoding into collection sets
mapx.ErrCollectionSetIncompatibleKindSentinel for string/numeric family mismatches in collection set elements
mapx.ErrCollectionSetOverflowSentinel for numeric overflow while converting collection set elements
mapx.ErrCollectionSetNonIntegerSentinel for fractional float values targeting integer set elements
mapx.ErrCollectionSetNotFiniteSentinel for NaN or infinity targeting integer set elements
mapx.ErrCollectionSetNegativeSentinel for negative values targeting unsigned set elements
mapx.ErrCollectionSetUnsupportedTargetSentinel for collection set element kinds without a conversion strategy
mapx.ErrJSONNumberNotIntegerSentinel for a fractional or exponent-form json.Number targeting an integer field (v0.38)
mapx.ErrJSONNumberOverflowSentinel for a json.Number that does not fit the numeric target type (v0.38)

The decoder hook chain translates json.Number values produced by number-preserving JSON parsing (v0.38): numeric targets get an exact digit parse with encoding/json-equivalent strictness, json.Number / json.RawMessage targets keep the literal, and every other target — most importantly any — sees float64, preserving the pre-json.Number runtime contract for dynamic consumers.

Struct to Map

import "github.com/coldsmirk/vef-framework-go/mapx"

type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}

user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
m, err := mapx.ToMap(user)
// m = map[string]any{"name": "Alice", "email": "alice@example.com", "age": 30}

Map to Struct

data := map[string]any{
"name": "Bob",
"email": "bob@example.com",
"age": 25,
}

user, err := mapx.FromMap[User](data)
// user.Name = "Bob", user.Email = "bob@example.com", user.Age = 25

Decoder Options

Both ToMap and FromMap accept optional DecoderOption values:

// Switch to a different tag, e.g. yaml
m, err := mapx.ToMap(user, mapx.WithTagName("yaml"))

// Weak type conversion (string "123" → int 123)
user, err := mapx.FromMap[User](data, mapx.WithWeaklyTypedInput())

// Surface fields present in the source map but absent from the struct
user, err := mapx.FromMap[User](data, mapx.WithErrorUnused())

Available Options

OptionEffect
WithTagName(tag)Override the struct tag mapx reads (default: json).
WithIgnoreUntaggedFields(ignore)Set whether fields without the active tag are ignored.
WithDecodeHook(hook)Replace the default decode hook.
WithMatchName(fn)Custom field-name matcher (default: exact match against lo.CamelCase(fieldName)).
WithErrorUnused()Fail when the source map carries keys not present on the struct.
WithErrorUnset()Fail when the struct has fields the source map didn't populate.
WithZeroFields()Zero out target struct fields before decoding.
WithAllowUnsetPointer()Allow pointer fields to remain nil instead of being initialized.
WithMetadata(m)Collect "unused" / "unset" key lists into a mapstructure.Metadata value.
WithWeaklyTypedInput()Coerce common type mismatches (string ↔ number ↔ bool …).
WithDecodeNil()Pass nil source values into the decode pipeline instead of skipping them.

Custom Decoder

For advanced use cases, create a reusable decoder:

var result User
decoder, err := mapx.NewDecoder(&result, mapx.WithTagName("yaml"))
if err != nil {
return err
}
err = decoder.Decode(data)

Decode Hooks

mapx ships a rich set of decode hooks pre-registered on NewDecoder, so plain-text maps coming from JSON, form data, or environment configs decode into typed structs without per-field wiring:

  • time.Time — parses "2006-01-02 15:04:05" (Go's time.DateTime layout)
  • time.Location — parses IANA names (e.g. "Asia/Shanghai")
  • time.Duration — parses Go duration strings (e.g. "5m")
  • *url.URL — parses URLs
  • net.IP / net.IPNet / netip.Addr / netip.AddrPort / netip.Prefix
  • json.RawMessage — marshals the source value to JSON bytes
  • *multipart.FileHeader — picks the only entry when the source is []*multipart.FileHeader with length 1
  • collections.Set / SortedSet / ConcurrentSet / ConcurrentSortedSet — turns a slice or array into the corresponding set type
  • encoding.TextUnmarshaler — any type that implements UnmarshalText
  • string → primitive coercions (int / uint / float / bool)

Collection-set decoding is registered for string, signed integers, unsigned integers, float32, and float64. It rejects nil elements, string/numeric family mismatches, numeric overflow, fractional floats targeting integer sets, NaN or infinity targeting integer sets, and negative values targeting unsigned sets.

timex.DateTime / timex.Date / timex.Time are defined as named types over time.Time; whether they hit the time.Time hook depends on mapstructure's underlying-type unwrapping. Verify case by case if you rely on automatic decoding for those types.

WithDecodeHook(myHook) replaces the default composed hook. To extend the defaults, compose your hook with mapx.DecoderHook before passing it to WithDecodeHook.

The composed default hook is also exported as mapx.DecoderHook, and metadata collection uses the exported alias mapx.Metadata.

Error Sentinels

ErrorMeaning
ErrInvalidToMapValueToMap received a non-struct value
ErrInvalidFromMapTypeFromMap[T] was instantiated with a non-struct T
ErrCollectionSetNilElementa nil element cannot be inserted into a collection set
ErrCollectionSetIncompatibleKindsource value kind does not match the set element kind
ErrCollectionSetOverflownumeric source value overflows the target set element type
ErrCollectionSetNonIntegerfractional float would lose data when decoded into an integer set
ErrCollectionSetNotFiniteNaN or infinity cannot be decoded into an integer set
ErrCollectionSetNegativenegative value cannot decode into an unsigned set element
ErrCollectionSetUnsupportedTargettarget set element kind has no conversion strategy