automapper-gen

module
v0.0.0-...-73c03bc Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 8, 2026 License: MIT

README ยถ

Automapper Generator

Go Version License

An easy-to-use CLI utility that generates reflection-free Go 1.18+ code for mapping structs with type conversion support.

โš ๏ธ Development Status: This project is in early development. There are yet no integrated tests, and some things may be subject to change. Any feedback is greatly appreciated!

Features

  • ๐Ÿš€ Zero Reflection: Generates type-safe code at compile time
  • ๐Ÿ“ฆ Remote Packages: Map from structs in any Go module (local or remote)
  • ๐ŸŒ Module Cache Support: Automatically loads types from Go's module cache
  • ๐Ÿ”„ Type Conversion: Convert any field types or formats via custom functions
  • ๐Ÿท๏ธ Flexible Mapping: Tag-based field mapping and transformations
  • ๐Ÿชบ Nesting: Nested models can be automatically handled
  • ๐Ÿ” Validation: Prevents generation of broken code, predicts problems, suggests solutions
  • โšก Performance: Direct field assignments, no runtime overhead

Marilyn Manson - No Reflection

Table of Contents

How It Works

  1. Parse Configuration: Reads automapper.json to understand the project setup
  2. Parse Source Code: Uses Go's AST parser to analyze structs and fields
  3. Extract Annotations: Finds // automapper:from=... comments on DTOs
  4. Match Fields: Maps source fields to target fields using name transforms and tags
  5. Generate Code: Uses jennifer to generate type-safe Go code
  6. Write Output: Creates the mapper file with all MapFrom methods
Generated Code Structure
/*
Code generated by automapper-gen. DO NOT EDIT.
Learn more: https://git.weirdcat.su/weirdcat/automapper-gen
*/

package dtos

import (
	"errors"
	"fmt"
	db "git.weirdcat.su/weirdcat/automapper-gen/example/db"
)

// MapFromUserDB maps from db.UserDB to UserDTO
func (d *UserDTO) MapFromUserDB(src *db.UserDB) error {
	if src == nil {
		return errors.New("source is nil")
	}

	d.ID = src.ID
	d.Username = src.Username
	{
		var err error
		d.Role, err = StrRoleToEnum(src.Role)
		if err != nil {
			return fmt.Errorf("converting field Role: %w", err)
		}
	}
	if src.About != nil {
		d.About = *src.About
	}
	// About: nil pointer will result in zero value
	{
		d.Pets = make([]PetDTO, len(src.Pets))
		for i, item := range src.Pets {
			var err error
			err = d.Pets[i].MapFromPetDB(&item)
			if err != nil {
				return fmt.Errorf("mapping nested field Pets[%d]: %w", i, err)
			}
		}
	}
	if src.FeaturedAchievement != nil {
		var nested AchievementDTO
		var err error
		err = nested.MapFromAchievementDB(src.FeaturedAchievement)
		if err != nil {
			return fmt.Errorf("mapping nested field FeaturedAchievement: %w", err)
		}
		d.FeaturedAchievement = nested
	}
	// FeaturedAchievement: nil pointer will result in zero value
	{
		var err error
		d.Interests, err = StrInterestsToEnums(src.Interests)
		if err != nil {
			return fmt.Errorf("converting field Interests: %w", err)
		}
	}
	if src.Birthday != nil {
		result := TimeToJSString(*src.Birthday)
		d.Birthday = &result
	}
	// Birthday: nil pointer will result in nil
	d.CreatedAt = TimeToJSString(src.CreatedAt)

	return nil
}

// MapFromPetDB maps from db.PetDB to PetDTO
func (d *PetDTO) MapFromPetDB(src *db.PetDB) error {
	if src == nil {
		return errors.New("source is nil")
	}

	d.ID = src.ID
	d.Name = src.Name
	{
		var err error
		d.Interests, err = StrInterestsToEnums(src.Interests)
		if err != nil {
			return fmt.Errorf("converting field Interests: %w", err)
		}
	}
	if src.Birthday != nil {
		result := TimeToJSString(*src.Birthday)
		d.Birthday = &result
	}
	// Birthday: nil pointer will result in nil
	d.CreatedAt = TimeToJSString(src.CreatedAt)

	return nil
}

// MapFromAchievementDB maps from db.AchievementDB to AchievementDTO
func (d *AchievementDTO) MapFromAchievementDB(src *db.AchievementDB) error {
	if src == nil {
		return errors.New("source is nil")
	}

	d.ID = src.ID
	d.Title = src.Title
	d.Description = ToLower(src.Description)

	return nil
}

Installation

From Source
# Clone the repository
git clone https://git.weirdcat.su/weirdcat/automapper-gen.git
cd automapper-gen

# Build and install
make install

# Or build only
make build
Using Go Install
go install git.weirdcat.su/weirdcat/automapper-gen/cmd/automapper-gen@latest

Quick Start

1. Create Configuration

Head to the directory where the destination structs or DTOs are supposed to be. Create an automapper.json:

{
  "output": "automappers.go",
  "converters": [],
  "externalPackages": [
    {
      "alias": "db",
      "importPath": "git.weirdcat.su/weirdcat/automapper-gen/example/db",
      "localPath": "../db"
    }
  ]
}

Note that since your source structs (e.g. database models) are most likely in a different package, we have to specify the package in externalPackages. In this example we use a database package from the same go module which is located in ./example/db. The alias option helps to avoid name collisions in the generated file when there are multiple external packages.

You may also use the localPath parameter which overrides the importPath. This may be useful if the generator is unable to discover a local package otherwise.

Note: External packages are normally loaded directly from Go's module cache. The package simply needs to be installed and added to externalPackages via the importPath.

2. Define Your Structs

Database Model (db/models.go):

package db

import "time"

type UserDB struct {
    ID        int64
    Username  string
    Email     string
    Password  string
}

This is what the generator is going to map from.

DTO Model (dtos/user.go):

package dtos

//automapper:from=db.UserDB
type UserDTO struct {
    ID        int64
    Username  string
    Email     string
}

This is the destination struct that the generator will attempt to map the previous struct to.

The database model requires none of our tags or annotations. This design choice was made for the sake of compatibility with database model generators that would otherwise overwrite the extra information. This makes the tool work especially well with the SQL-compiler sqlc.

3. Generate Mappers

Simply run the command in the second directory:

# From the dtos directory
automapper-gen .

Great, the code has been generated and written into the automappers.go file in the same directory. The file now declares a method for our UserDTO destination struct, which will map data from the source struct db.UserDB:

(d* UserDTO) MapFromUserDB(src* db.UserDB)

the mapper methods always follow the pattern obj.MapFromT(src T).

Automapper Generator utilizes its own validation system to find errors and warn the user about potential problems. However, if it fails and still generates faulty code, we are still safe, since the code will not compile and the problem will be noticed immediately, unlike with reflection-based mappers.

4. Use the Generated Code

Usage of the generated code is straightforward:

package main

import (
    "fmt"
    "time"
    "yourproject/db"
    "yourproject/dtos"
)

func main() {
    // Source data
    user := &db.UserDB{
        ID:        1,
        Username:  "john_doe",
        Email:     "john@example.com",
        Password:  "hashed_password",
    }

    // Map to DTO
    dto := &dtos.UserDTO{}
    if err := dto.MapFromUserDB(user); err != nil {
        panic(err)
    }

    fmt.Printf("User: %+v\n", dto)
    // Output: User: {ID:1 Username:john_doe Email:john@example.com}
}

Configuration

Configuration File (automapper.json)
Field Type Required Description
output string No Output filename (default: "automappers.go")
converters array No List converters with name and function
nilPointersForNull bool No Use nil pointers for null values
externalPackages array No External packages to parse
External Packages

External packages are loaded directly from Go's module cache, making it easy to map from types in any Go module:

{
  "externalPackages": [
    {
      "alias": "db",
      "importPath": "github.com/yourorg/project/db"
    },
    {
      "alias": "models",
      "importPath": "git.example.com/team/service/models"
    }
  ]
}

Local Development: If you're working on a module locally and want to use local changes:

{
  "externalPackages": [
    {
      "alias": "db",
      "importPath": "github.com/yourorg/project/db",
      "localPath": "../db"
    }
  ]
}

The generator will try the local path first, then fall back to the module cache.

Usage

Remote Modules

One of the key features is the ability to map from types in any Go module, whether it's in your repository or a completely separate one:

Example: Mapping from a Different Repository

Repository 1 (git.weirdcat.su/test/prj1):

// bd/models.go
package bd

import "time"

type User struct {
    ID        int64
    Username  string
    CreatedAt time.Time
}

Repository 2 (git.weirdcat.su/test/prj2):

// dto/automapper.json
{
  "output": "automappers.go",
  "externalPackages": [
    {
      "alias": "db",
      "importPath": "git.weirdcat.su/test/prj1/bd"
    }
  ]
}
// dto/user.go
package dto

//automapper:from=db.User
type UserDTO struct {
    ID        int64
    Username  string
    CreatedAt string `automapper:"converter=TimeToJSString"`
}

Prerequisites: Make sure the external module is in your go.mod:

go get git.weirdcat.su/test/prj1

Then generate:

cd dto
automapper-gen .

The generator will load the bd package from your module cache and generate the appropriate mappers!

Basic Mapping
//automapper:from=SourceStruct
type TargetDTO struct {
    Field1 string
    Field2 int
}
Multiple Source Structs

It is possible to specify multiple source structs in the automapper:from annotation:

//automapper:from=User,Profile
type UserProfileDTO struct {
    // Will generate MapFromUser and MapFromProfile
    Name  string
    Email string
}

Two seperate methods will be created. This is useful when there are two database requests that return different objects with identical or similar contents.

Field Tags
Skip Field
type UserDTO struct {
    Password string `automapper:"-"`  // Will not be mapped
}
Custom Field Mapping

We are able to handle field name mismatches using the field parameter:

type UserDTO struct {
    Name string `automapper:"field=Full_Name"`  // Maps from Full_Name
}
Field Converter

More info the converters section.

type UserDTO struct {
    CreatedAt string `automapper:"converter=TimeToJSString"`
}
Combined Tags
type UserDTO struct {
    BirthDate string `automapper:"field=Birthday,converter=TimeToJSString"`
}
Converters

We often have to deal with mismatching data types or want to adjust the format of the data. We can automate conversion by implementing custom converters in the package to do the work for us.

In the same package where we store the destination structs, create a new .go file. We suggest the name converters.go. Write one or several conversion functions:

package dtos

import (
    "fmt"
    "git.weirdcat.su/weirdcat/automapper-gen/example/types"
)

// Regular converter
func StrRoleToEnum(role string) (types.Role, error) {
    switch role {
    case "admin":
        return types.RoleAdmin, nil
    case "user":
        return types.RoleUser, nil
    default:
        return types.RoleGuest, fmt.Errorf("unknown role: %s", role)
    }
}

// Safe converter
func ToLower(s string) string {
	return strings.ToLower(s)
}

Note: Converter functions must follow the signature func(T) (U, error) or func(T) (U) and be in the same package as your DTOs.

Update your automapper.json to include your converters:

{
  "output": "automappers.go",
  "converters": [
    {
      "name": "RoleEnum",
      "function": "StrRoleToEnum"
    },
    {
      "name": "ToLower",
      "function": "Lowercase"
    }
  ],
  "externalPackages": [
    {
      "alias": "db",
      "importPath": "git.weirdcat.su/weirdcat/automapper-gen/example/db"
    }
  ]
}

The name parameter defines the tag which will be used in our destination structs to mark the conversion strategy for a given field.

The function parameter references the name of the function that we created. The function will be tied to the previously defined tag name.

Use in your DTOs:

type UserDTO struct {
    Role      Role       `automapper:"converter=RoleEnum"`
    Bio       string     `automapper:"converter=LowerCase"`
}
Nested Structs

The nested struct feature allows automatic mapping of complex nested structures without manual field-by-field copying. When a source struct contains fields that should map to other DTOs, you can use the dto tag to trigger automatic nested mapping:

type UserDTO struct {
	ID                  int64
	Username            string
	Pets                []PetDTO       `automapper:"dto=PetDTO"`
	FeaturedAchievement AchievementDTO `automapper:"dto=AchievementDTO"`
}

//automapper:from=db.PetDB
type PetDTO struct {
	ID        int64
	Name      string
}

//automapper:from=db.AchievementDB
type AchievementDTO struct {
	ID          int64
	Title       string
}

The generator will recursively handle the nested structures and generate code that reuses the MapFrom methods for the inner structs, so that mapping, convertion and validation will run normally.

The dto tag can also be combined with other automapper options:

// Just nested DTO
Field DTO `automapper:"dto=TargetDTO"`

// Nested DTO with custom field mapping
Field DTO `automapper:"dto=TargetDTO,field=SourceFieldName"`

// Cannot combine dto with converter (dto takes precedence)

Acknowledgments

  • jennifer - Go code generation library
  • Inspired by sqlc
  • Inspired by AutoMapper in .NET

Directories ยถ

Path Synopsis
cmd
automapper-gen command
example
cmd command
db
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL