An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

## go-mapper-gen

[![Go Report Card](https://goreportcard.com/badge/github.com/toniphan21/go-bf)](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!