initial commit. Cloned timetracker repository
This commit is contained in:
74
app/utils/mapper/mapper.go
Normal file
74
app/utils/mapper/mapper.go
Normal file
@@ -0,0 +1,74 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user