Code generation tool that parses Go struct definitions and generates type-safe mapping functions. Zero reflection, zero runtime cost. In func mode (default), the generated code has zero dependencies.
go install github.com/KARTIKrocks/gomapper@latestAdd a go:generate directive to your Go file:
//go:generate gomapper -src User -dst UserDTOThen run:
go generate ./...This produces a mapper_gen.go file with pure mapping functions — no runtime dependencies.
//go:generate gomapper -pairs User:UserDTO,Order:OrderDTO| Flag | Default | Description |
|---|---|---|
-src |
Source type name | |
-dst |
Destination type name | |
-pairs |
Comma-separated Src:Dst pairs |
|
-output |
mapper_gen.go |
Output file name |
-mode |
func |
func (pure functions), register, or both |
-bidirectional |
false |
Generate both S→D and D→S mappings |
-tag |
map |
Struct tag key for field renaming |
-strict |
false |
Fail if any destination field is unmapped |
-ci |
false |
Case-insensitive field name matching |
-nil-safe |
false |
Generate nil checks for pointer dereferences |
-v |
false |
Verbose output |
func(default) — generates pureMapSrcToDstfunctions. No imports, no reflection, compile-time type safety. Slice fields produce inline loops.register— generatesmapper.Register(...)calls for the mapper library's runtime registry.both— generates named functions and registers them viamapper.Register.
Fields are matched in this order:
- Fields with
map:"-"tag are skipped entirely (on either source or destination) - Destination field
map:"SourcePath"tag (supports dot notation for nested structs) - Source field
map:"DstName"tag - Exact name match with assignable type → direct assignment
- Exact name match with convertible type → type conversion
- Case-insensitive name match (with
-ciflag) —UserNamematchesUsername - Pointer handling:
*T→T(dereference),T→*T(address-of),*T→U(deref + convert) - Nested struct mapping:
Address→AddressDTOgeneratesMapAddressToAddressDTO(src.Addr)(func/both) ormapper.Map[AddressDTO](src.Addr)(register) - Slice handling:
[]T→[]Ugenerates inline loop callingMapTToU(func/both) ormapper.MapSlice(register) - No match →
// TODO: unmapped fieldcomment (or error in-strictmode)
Unexported fields are skipped. Embedded struct promoted fields are included.
Input:
type User struct {
ID int
Name string
Email string
}
type UserDTO struct {
ID int
Name string
}Generated (mapper_gen.go):
// Code generated by gomapper; DO NOT EDIT.
package mypackage
func MapUserToUserDTO(src User) UserDTO {
return UserDTO{
ID: src.ID,
Name: src.Name,
}
}No imports. No reflection. Just a function.
Use -ci when source and destination fields differ only by casing:
type CISrc struct { UserName string; EMail string }
type CIDst struct { Username string; Email string }gomapper -src CISrc -dst CIDst -ciExact matches always take priority — -ci is only a fallback.
When both source and destination fields are different named struct types, gomapper generates a call to the corresponding mapping function:
type Address struct { Street, City string }
type AddressDTO struct { Street, City string }
type Order struct { ID int; Addr Address }
type OrderDTO struct { ID int; Addr AddressDTO }gomapper -pairs Address:AddressDTO,Order:OrderDTOGenerated:
func MapOrderToOrderDTO(src Order) OrderDTO {
return OrderDTO{
ID: src.ID,
Addr: MapAddressToAddressDTO(src.Addr),
}
}In register mode, nested struct fields use mapper.Map[AddressDTO](src.Addr) instead.
By default, pointer-to-value mappings generate bare dereferences (*src.Name) which panic on nil. Use -nil-safe to generate nil checks:
type PtrSrc struct { Name *string; Age *int }
type PtrDst struct { Name string; Age int64 }gomapper -src PtrSrc -dst PtrDst -nil-safeGenerated:
func MapPtrSrcToPtrDst(src PtrSrc) PtrDst {
var _Name string
if src.Name != nil {
_Name = *src.Name
}
var _Age int64
if src.Age != nil {
_Age = int64(*src.Age)
}
return PtrDst{
Name: _Name,
Age: _Age,
}
}Without -nil-safe, pointer fields use the original *src.Name / int64(*src.Age) expressions.
- Multi-level pointers (
**T,***T) are not dereferenced — only single-level*T→Tis handled. Fields with deeper pointer indirection will appear as// TODO: unmapped.