goverter
a "type-safe Go converter" generator
goverter is a tool for creating type-safe converters. All you have to
do is create an interface and execute goverter. The project is meant as
alternative to jinzhu/copier that doesn't
use reflection.
Features
- Automatic conversion of builtin types
(
house
example), this includes:
- slices, maps, named types, primitive types, pointers
- structs with same fields
- Extend parts of the conversion with your own implementation:
Docs
- Optional return of an error: Docs
- Awesome error
messages: mismatch type test
- No reflection in the generated code
Usage
-
Create a go modules project if you haven't done so already
$ go mod init module-name
-
Add goverter
as dependency to your project
$ go get github.com/polpo-studio/goverter
-
Create your converter interface and mark it with a comment containing goverter:converter
input.go
package example
// goverter:converter
type Converter interface {
Convert(source []Input) []Output
}
type Input struct {
Name string
Age int
}
type Output struct {
Name string
Age int
}
-
Run goverter
:
$ go run github.com/polpo-studio/goverter/cmd/goverter [package-name-in-full or relative-path-starts-with-a-dot]
# example
$ go run github.com/polpo-studio/goverter/cmd/goverter github.com/polpo-studio/goverter/example/simple
$ go run github.com/polpo-studio/goverter/cmd/goverter ./example/simple
Note that relative path must start with a dot to indicate it is not a golang package. Omiting the dot (e.g. example/simple) may not work.
-
goverter created a file at ./generated/generated.go
, it may look like this:
package generated
import simple "github.com/polpo-studio/goverter/example/simple"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source []simple.Input) []simple.Output {
simpleOutputList := make([]simple.Output, len(source))
for i := 0; i < len(source); i++ {
simpleOutputList[i] = c.simpleInputToSimpleOutput(source[i])
}
return simpleOutputList
}
func (c *ConverterImpl) simpleInputToSimpleOutput(source simple.Input) simple.Output {
var simpleOutput simple.Output
simpleOutput.Name = source.Name
simpleOutput.Age = source.Age
return simpleOutput
}
Docs
Rename converter
With goverter:name
you can set the name of the generated converter struct.
input.go
// goverter:converter
// goverter:name RenamedConverter
type BadlyNamed interface {
// .. methods
}
output.go
type RenamedConverter struct {}
func (c *RenamedConverter) ...
Extend with custom implementation
With goverter:extend
you can instruct goverter to use conversion methods from the same package as
the conversion interface or from other packages.
You can pass multiple extend statements to goverter:extend
, or define the tag multiple times. Each
statement can be:
Note: The function name can be a regular expression.
// search for conversion methods that start with SQLStringTo in converter's package
// goverter:extend SQLStringTo.*
// the example below enables ParseBool method
// goverter:extend strconv:Parse.*
See house
example sql.NullString
input.go
// goverter:converter
// goverter:extend IntToString
type Converter interface {
Convert(Input) Output
}
type Input struct {Value int}
type Output struct {Value string}
// You must atleast define a source and target type. Meaning one parameter and one return.
// You can use any type you want, like struct, maps and so on.
func IntToString(i int) string {
return fmt.Sprint(i)
}
Reuse generated converter
If you need access to the generated converter, you can define it as first
parameter.
func IntToString(c Converter, i int) string {
// c.DoSomething()..
return fmt.Sprint(i)
}
Errors
Sometimes, custom conversion may fail, in this case goverter allows you to
define a second return parameter which must be type error
.
// goverter:converter
// goverter:extend IntToString
type Converter interface {
Convert(Input) (Output, error)
}
type Input struct {Value int}
type Output struct {Value string}
func IntToString(i int) (string, error) {
if i == 0 {
return "", errors.New("zero is not allowed")
}
return fmt.Sprint(i)
}
Note: If you do this, methods on the interface that'll use this custom
implementation, must also return error as second return.
Struct field mapping
With goverter:map
you can map fields on structs that have the same type.
goverter:map
takes 2 parameters.
- source field path (fields are separated by
.
)
- target field name
// goverter:converter
type Converter interface {
// goverter:map Name FullName
// goverter:map Nested.Age Age
Convert(source Input) Output
}
type Input struct {
Name string
Nested NestedInput
}
type NestedInput struct {
Age int
}
type Output struct {
FullName string
Age int
}
Case-insensitive field matching
With goverter:matchIgnoreCase
tag you can instruct goverter to perform case-insensitive mapping
between source and target fields. If this tag is present on a conversion method, goverter matches
the fields using strings.EqualFold method.
Use this tag only when it is extremely unlikely for the source or the target to have two fields
that differ only in casing. E.g.: converting go-jet generated model to protoc generated struct.
If matchIgnoreCase
is present and goverter detects an ambiquous match, it either prefers an exact
match (if found) or reports an error. Use goverter:map to fix an ambiquous match error.
goverter:matchIgnoreCase
takes no parameters.
// goverter:converter
type Converter interface {
// goverter:matchIgnoreCase
// goverter:map FullId FullID
Convert(source Input) Output
}
type Input struct {
Uuid string
FullId int
fullId int
}
type Output struct {
UUID string // auto-matched with Uuid due to goverter:matchIgnoreCase
FullID string // mapped to FullId, to resolve ambiguity
}
Struct identity mapping
With goverter:mapIdentity
you can instruct goverter to use the source struct
as source for the conversion to the target property.
goverter:mapIdentity
takes multiple field names separated by space
.
// goverter:converter
type Converter interface {
// goverter:mapIdentity Address
ConvertPerson(source Person) APIPerson
}
type Person struct {
Name string
Street string
City string
}
type APIPerson struct {
Name string
Address APIAddress
}
type APIAddress struct {
Street string
City string
}
In the example goverter will fill Address
by creating a converter from
Person
to APIAddress
. Example generated code:
func (c *ConverterImpl) ConvertPerson(source execution.Person) execution.APIPerson {
var structsAPIPerson execution.APIPerson
structsAPIPerson.Name = source.Name
structsAPIPerson.Address = c.structsPersonToStructsAPIAddress(source)
return structsAPIPerson
}
func (c *ConverterImpl) structsPersonToStructsAPIAddress(source execution.Person) execution.APIAddress {
var structsAPIAddress execution.APIAddress
structsAPIAddress.Street = source.Street
structsAPIAddress.City = source.City
return structsAPIAddress
}
Struct ignore field
With goverter:ignore
you can ignore fields on the target struct
goverter:ignore
takes multiple field names separated by space
.
// goverter:converter
type Converter interface {
// goverter:ignore Age
Convert(source Input) Output
}
type Input struct {
Name string
}
type Output struct {
Name string
Age int
}
Versioning
goverter use SemVer for versioning the cli.
License
This project is licensed under the MIT License - see the LICENSE
file for details
Logo by MariaLetta