75 lines
2.0 KiB
Go
75 lines
2.0 KiB
Go
package mapper
|
||
|
||
// Package mapper provides utilities to map fields from one struct to another
|
||
// by matching field names (case-insensitive). Unmatched fields are left as
|
||
// their zero values.
|
||
|
||
import (
|
||
"fmt"
|
||
"reflect"
|
||
"strings"
|
||
)
|
||
|
||
// Map copies field values from src into dst by matching field names
|
||
// (case-insensitive). Fields in dst that have no counterpart in src
|
||
// are left at their zero value.
|
||
//
|
||
// Both dst and src must be pointers to structs.
|
||
// Returns an error if the types do not satisfy those constraints.
|
||
func Map(dst, src any) error {
|
||
dstVal := reflect.ValueOf(dst)
|
||
srcVal := reflect.ValueOf(src)
|
||
|
||
if dstVal.Kind() != reflect.Ptr || dstVal.Elem().Kind() != reflect.Struct {
|
||
return fmt.Errorf("mapper: dst must be a pointer to a struct, got %T", dst)
|
||
}
|
||
if srcVal.Kind() != reflect.Ptr || srcVal.Elem().Kind() != reflect.Struct {
|
||
return fmt.Errorf("mapper: src must be a pointer to a struct, got %T", src)
|
||
}
|
||
|
||
dstElem := dstVal.Elem()
|
||
srcElem := srcVal.Elem()
|
||
|
||
// Build a lookup map of src fields: lowercase name -> field value
|
||
srcFields := make(map[string]reflect.Value)
|
||
for i := 0; i < srcElem.NumField(); i++ {
|
||
f := srcElem.Type().Field(i)
|
||
if !f.IsExported() {
|
||
continue
|
||
}
|
||
srcFields[strings.ToLower(f.Name)] = srcElem.Field(i)
|
||
}
|
||
|
||
// Iterate over dst fields and copy matching src values
|
||
for i := 0; i < dstElem.NumField(); i++ {
|
||
dstField := dstElem.Field(i)
|
||
dstType := dstElem.Type().Field(i)
|
||
|
||
if !dstType.IsExported() || !dstField.CanSet() {
|
||
continue
|
||
}
|
||
|
||
srcField, ok := srcFields[strings.ToLower(dstType.Name)]
|
||
if !ok {
|
||
// No matching src field – leave zero value in place
|
||
continue
|
||
}
|
||
|
||
if srcField.Type().AssignableTo(dstField.Type()) {
|
||
dstField.Set(srcField)
|
||
} else if srcField.Type().ConvertibleTo(dstField.Type()) {
|
||
dstField.Set(srcField.Convert(dstField.Type()))
|
||
}
|
||
// If neither assignable nor convertible, the dst field keeps its zero value
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// MustMap is like Map but panics on error.
|
||
func MustMap(dst, src any) {
|
||
if err := Map(dst, src); err != nil {
|
||
panic(err)
|
||
}
|
||
}
|