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) } }