https://github.com/toniphan21/go-mapper-gen
A type-safe code generator for Go that automates mappings between different struct types.
https://github.com/toniphan21/go-mapper-gen
code-generator compiled-time generator go golang mapper mapping pkl structs
Last synced: about 1 month ago
JSON representation
A type-safe code generator for Go that automates mappings between different struct types.
- Host: GitHub
- URL: https://github.com/toniphan21/go-mapper-gen
- Owner: toniphan21
- License: mit
- Created: 2025-12-08T19:15:26.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-01-11T15:03:42.000Z (about 2 months ago)
- Last Synced: 2026-01-11T18:04:09.699Z (about 2 months ago)
- Topics: code-generator, compiled-time, generator, go, golang, mapper, mapping, pkl, structs
- Language: Go
- Homepage:
- Size: 577 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
## go-mapper-gen
[](https://goreportcard.com/report/github.com/toniphan21/go-bf)
---
**go-mapper-gen** is a type-safe code generator for Go that automates mappings between different struct types such
as domain entities ↔︎ database models or API ↔︎ internal representations.
It uses [pkl](https://pkl-lang.org) as a modern, schema-driven configuration language, providing strong validation,
clear semantics, and IDE auto-completion when defining mappings.
### Highlights
- 🔒 Type-safe code generation with compile-time guarantees.
- 🧘 Flexible output: functions or interface/implementation pairs.
- 💪 Strongly typed [pkl](https://pkl-lang.org) configuration schema with IDE support.
- 🧰 Built-in support for common field types, extensible via custom functions.
- 🛠️ Can be used as a standalone CLI or embedded as a Go library.
---
### Quickstart
Firstly, let set up a project which uses `sqlc` and `pgtype`
```go.mod
module github.com/toniphan21/go-mapper-gen/basic
go 1.25
require github.com/jackc/pgx/v5 v5.7.6
```
the `go.sum` file is
```go.sum
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
```
Given that you have an entity located in your `domain` package:
```go
// file: domain/entity.go
//go:generate go run github.com/toniphan21/go-mapper-gen/cmd/generator
package domain
type User struct {
ID string
FirstName string
LastName string
Email string
Password *string
}
```
and another struct perhaps generated from your database using tool such as [sqlc](https://sqlc.dev) located in `db`
```go
// file: db/models.go
package db
import "github.com/jackc/pgx/v5/pgtype"
type User struct {
ID string
Email string
FirstName string
LastName string
Password pgtype.Text
}
```
---
#### Default mode: types (interface + implementation)
With this minimal configuration, `go-mapper-gen` generates an unexported interface and implementation with methods to
convert both ways.
```pkl
// file: mapper.pkl
amends "https://github.com/toniphan21/go-mapper-gen/releases/download/current/Config.pkl"
local function package(path: String) = "github.com/toniphan21/go-mapper-gen/basic/" + path
packages {
[package("db")] {
source_pkg = package("domain")
structs {
["User"] {}
}
}
}
```
To generate code:
- Run `go generate ./...`, or
- Invoke directly: `go run github.com/toniphan21/go-mapper-gen/cmd/generator` (from the module root)
This produces mapping code (by default in the target package) and keeps names unexported to avoid polluting your API surface.
```go
// golden-file: db/gen_mapper.go
// Code generated by github.com/toniphan21/go-mapper-gen - test, DO NOT EDIT.
package db
import (
pgtype "github.com/jackc/pgx/v5/pgtype"
domain "github.com/toniphan21/go-mapper-gen/basic/domain"
)
type iMapper interface {
// ToUser converts a domain.User value into a User value.
ToUser(in domain.User) User
// FromUser converts a User value into a domain.User value.
FromUser(in User) domain.User
}
func new_iMapper() iMapper {
return &iMapperImpl{}
}
type iMapperImpl struct{}
func (m *iMapperImpl) ToUser(in domain.User) User {
var out User
out.ID = in.ID
out.Email = in.Email
out.FirstName = in.FirstName
out.LastName = in.LastName
if in.Password != nil {
out.Password = pgtype.Text{
String: *in.Password,
Valid: true,
}
}
return out
}
func (m *iMapperImpl) FromUser(in User) domain.User {
var out domain.User
out.ID = in.ID
out.FirstName = in.FirstName
out.LastName = in.LastName
out.Email = in.Email
if in.Password.Valid {
out.Password = &in.Password.String
}
return out
}
var _ iMapper = (*iMapperImpl)(nil)
```
Use via composition to keep your API clean:
```go
// file: db/mapper.go
var mapper = new_iMapper()
func demo() {
_ = mapper.ToUser(/* ... */)
}
// Or expose your own interface that embeds the generated one
type Mapper interface { iMapper }
```
You can customize names (interface, implementation, constructor) in the Pkl config.
See [Config.pkl](https://github.com/toniphan21/go-mapper-gen/blob/main/pkl/Config.pkl),
[mapper.pkl](https://github.com/toniphan21/go-mapper-gen/blob/main/pkl/mapper.pkl) for all options.
---
#### Functional mode: package-level functions
Switch to functions mode to emit only functions:
```pkl
// file: mapper.pkl
amends "https://github.com/toniphan21/go-mapper-gen/releases/download/current/Config.pkl"
local function package(path: String) = "github.com/toniphan21/go-mapper-gen/basic/" + path
packages {
[package("db")] {
mode = "functions"
source_pkg = package("domain")
structs {
["User"] {}
}
}
}
```
This yields functions like `ToUser` and `FromUser` in the target package:
```go
// golden-file: db/gen_mapper.go
// Code generated by github.com/toniphan21/go-mapper-gen - test, DO NOT EDIT.
package db
import (
pgtype "github.com/jackc/pgx/v5/pgtype"
domain "github.com/toniphan21/go-mapper-gen/basic/domain"
)
// ToUser converts a domain.User value into a User value.
func ToUser(in domain.User) User {
var out User
out.ID = in.ID
out.Email = in.Email
out.FirstName = in.FirstName
out.LastName = in.LastName
if in.Password != nil {
out.Password = pgtype.Text{
String: *in.Password,
Valid: true,
}
}
return out
}
// FromUser converts a User value into a domain.User value.
func FromUser(in User) domain.User {
var out domain.User
out.ID = in.ID
out.FirstName = in.FirstName
out.LastName = in.LastName
out.Email = in.Email
if in.Password.Valid {
out.Password = &in.Password.String
}
return out
}
```
---
### Next Steps
Take a look at examples of how to:
- Config
[multiple structs](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/config/02-multiple-structs),
[change functions' name](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/config/03-change-functions-name),
[multiple mappers in a package](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/config/01-multiple-mappers).
- Convert custom type:
[with package level functions](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/functions-converter/01-use-package-level-functions),
[with variable methods](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/functions-converter/02-use-variable-methods).
- [Manual mapping fields](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/field-mapping/02-manual-mapping-fields),
[use function/method to convert individual field](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/field-mapping/03-use-function-on-individual-field).
- [Use go-mapper-gen as a library.](https://github.com/toniphan21/go-mapper-gen/tree/main/examples/use-as-library)
---
### Contributing & Licence
PRs are welcome! See the [CONTRIBUTING](https://github.com/toniphan21/go-mapper-gen/blob/main/CONTRIBUTING.md).
Distributed under the MIT License.
❤️ Like the project? [Buy me a coffee](https://buymeacoffee.com/toniphan21) ☕. Thank you!