mapstructure

This commit is contained in:
adman
2026-04-20 23:16:01 +02:00
parent 997594d7c3
commit 8de34dfda1
6 changed files with 1005 additions and 4 deletions
+110
View File
@@ -0,0 +1,110 @@
package mapstructure
import (
"errors"
"reflect"
)
var (
ErrNotPointer = errors.New("target must be a pointer")
ErrNilPointer = errors.New("target must not be a nil pointer")
ErrNotStruct = errors.New("target must be a pointer to a struct")
ErrNotMap = errors.New("data must be a map[string]any")
ErrMissingRequired = errors.New("missing required field")
ErrExtraKey = errors.New("map contains key with no matching struct field")
)
type Format struct {
Name string
requireAll bool
errorOnExtra bool
decoders map[reflect.Type]func(any, string) (any, error)
encoders map[reflect.Type]func(any, string) (any, error)
}
func NewFormat(name string, opts ...Option) *Format {
f := &Format{
Name: name,
decoders: make(map[reflect.Type]func(any, string) (any, error)),
encoders: make(map[reflect.Type]func(any, string) (any, error)),
}
for _, opt := range opts {
opt(f)
}
return f
}
type Option func(*Format)
func WithRequireAll(require bool) Option {
return func(f *Format) {
f.requireAll = require
}
}
func WithErrorOnExtra(errorOnExtra bool) Option {
return func(f *Format) {
f.errorOnExtra = errorOnExtra
}
}
func WithTypeCodec(decodeFn, encodeFn any) Option {
dt := reflect.TypeOf(decodeFn)
et := reflect.TypeOf(encodeFn)
if dt.Kind() != reflect.Func || dt.NumIn() != 2 || dt.NumOut() != 2 {
panic("decode function must have signature func(any, string) (T, error)")
}
if et.Kind() != reflect.Func || et.NumIn() != 2 || et.NumOut() != 2 {
panic("encode function must have signature func(T, string) (any, error)")
}
targetType := dt.Out(0)
sourceType := et.In(0)
return func(f *Format) {
dv := reflect.ValueOf(decodeFn)
f.decoders[targetType] = func(v any, tag string) (any, error) {
results := dv.Call([]reflect.Value{reflect.ValueOf(v), reflect.ValueOf(tag)})
err, _ := results[1].Interface().(error)
return results[0].Interface(), err
}
ev := reflect.ValueOf(encodeFn)
f.encoders[sourceType] = func(v any, tag string) (any, error) {
results := ev.Call([]reflect.Value{reflect.ValueOf(v), reflect.ValueOf(tag)})
err, _ := results[1].Interface().(error)
return results[0].Interface(), err
}
}
}
func (f *Format) Decode(data any, target any) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Pointer || v.IsNil() {
return ErrNotPointer
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return ErrNotStruct
}
m, ok := data.(map[string]any)
if !ok {
return ErrNotMap
}
return f.decode(m, v)
}
func (f *Format) Encode(source any) (map[string]any, error) {
v := reflect.ValueOf(source)
if v.Kind() == reflect.Pointer {
if v.IsNil() {
return nil, ErrNilPointer
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, ErrNotStruct
}
return f.encode(v)
}