CLI tool
jmattheis/goverter avatar
jmattheis/goverter

goverter: generating type-safe Go converters from an interface you write

Generate type-safe Go converters by defining function signatures.

889 stars70 forksGoMIT

At a glance

What is it?
goverter turns a Go interface into concrete conversion functions at build time, so a struct-to-struct mapping is checked by the compiler rather than discovered at run time. It is an alternative to reflection-based copying libraries, it fails early when fields cannot be mapped, and the interface you declare is the only thing you have to write.
Who is it for?
goverter fits a Go codebase that keeps a domain model and an API model as separate types and has grown tired of hand written conversion functions that silently drop fields, because the compiler becomes the thing that tells you a field was forgotten.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

You write an interface, the generator writes the functions

The core idea is compact. The project documentation says all you have to do is create an interface and execute goverter, and the repository description is generate type-safe Go converters by defining function signatures.

The example in the README shows the whole flow. The input is an interface with a goverter:converter marker comment, two methods, and two structs:

go
// goverter:converter
type Converter interface {
    ConvertItems(source []Input) []Output

    // goverter:ignore Irrelevant
    // goverter:map Nested.AgeInYears Age
    Convert(source Input) Output
}

The methods on that interface are the conversion functions you want. The parameters and results are the types you want to convert between. Comments on the interface refine the behaviour, and the goverter:ignore and goverter:map markers are two of them: one says do not carry this field across, the other says take this nested field and put it in that field.

The output is generated Go, in a separate package:

go
func (c *ConverterImpl) Convert(source example.Input) example.Output {
    var exampleOutput example.Output
    exampleOutput.Name = source.Name
    exampleOutput.Age = source.Nested.AgeInYears
    return exampleOutput
}

There is no runtime component. The generated code assigns fields directly, with no reflection and no type assertions at run time, which is the performance and safety argument in one sentence. The topic list on the project record names the mechanism: code-generation, converter, copy, generator, go and struct.

The alternative the documentation positions against is jinzhu/copier, and the difference in approach is explicit: goverter is meant as an alternative that does not use reflection. That is the whole design decision, and it is worth understanding what it costs before you adopt it.

The generated code is deliberately boring, and that is the point

Look at what the generator emits for a slice conversion:

go
func (c *ConverterImpl) ConvertItems(source []example.Input) []example.Output {
    var exampleOutputList []example.Output
    if source != nil {
        exampleOutputList = make([]example.Output, len(source))
        for i := 0; i < len(source); i++ {
            exampleOutputList[i] = c.Convert(source[i])
        }
    }
    return exampleOutputList
}

Three things are visible there. The nil check preserves the distinction between a nil slice and an empty slice, which matters in JSON encoding and in equality checks. The make call allocates exactly once for the whole slice, rather than appending. And the per-element conversion delegates to the single-item method rather than duplicating the field logic.

None of this is clever, and that is the design goal. A reflection-based copier does the same work at run time by walking struct tags and doing type assertions, which means a field whose type does not line up becomes a run time error or a silently skipped field. Here the same mistake is a compile error in generated code, or an error at generation time, and the generated function is something a reader can follow without knowing the generator exists.

That last property has a practical consequence for a team. A code review of a conversion function generated by goverter is a review of boring code, and the interesting part, the interface with its markers, is small enough to review carefully. The alternative, a hand written mapper with fifty assignments, is exactly the kind of thing reviewers skim.

It also means the generated code is safe to commit. Since there is no runtime dependency on the generator, the built binary does not carry it, and the conversion behaviour of your application is fixed by the code in the repository rather than by the version of a library you happened to depend on.

Early errors are the feature that justifies a code generator

The features list names a set of errors that are clear when generating the conversion methods, and the two cases given are the ones that hurt most in practice.

The first is a target struct with unmapped fields. If the output type has a field that the input type cannot supply, the generator reports it rather than leaving it at its zero value. The second is types that cannot be converted without losing information.

Both of these are silent failures with a reflection-based copier. A struct with an extra field gets a zero value that looks plausible until somebody notices the field is always empty. Two types that share a name but not a representation get a conversion that truncates or overflows. The zero value is the most dangerous value in Go, because it is often a valid thing for the field to be.

The features list also covers what the generator handles by default. It automatically converts builtin types, naming slices, maps, named types, primitive types and pointers, and structs with the same fields. It supports enums, with a dedicated guide page. It performs deep copies by default and supports shallow copying. And it is customizable, because you can implement custom converter methods.

The custom method extension point is the one that decides how much of the mapping you write by hand. The repository listing shows a large set of example directories, and the names describe the design space: extend-local, extend-local-complex, extend-local-with-converter, extend-external, extend-with-error, map-custom, map-field, map-path, map-identity, constructor, context, default, default-update, embedded, enum, format, any-to-any, auto-map, ignore, ignore-missing, ignore-unexported, house, annotate-unmapped and errors. Several of those are self-describing in a way that tells you the library handles the awkward cases rather than only the easy ones.

There is also a dedicated guide page for error-early behaviour, and the test scenarios live in a scenario directory in the repository, which the documentation points at as evidence of thorough testing.

What the generator is built on, and what that implies for your build

The go.mod file is short and tells you the shape of the tool.

The module is github.com/jmattheis/goverter, it requires Go 1.26.0, and the direct dependencies are four. dave/jennifer at 1.6.0 is the code generation library, and it is the reason the generated output is idiomatic: Jennifer builds Go source through an AST rather than by writing strings, so the result is properly formatted and gofmt clean. golang.org/x/tools at 0.51.0 is the type checking and package loading machinery. gopkg.in/yaml.v3 is the configuration format. And stretchr/testify is the test library.

Four dependencies, and one of them is the reason the tool exists in the form it does. A string templating generator would have produced the same assignments with less machinery, but it would also have produced output that occasionally did not compile because of a name collision or a shadowed import. Jennifer cannot emit a program that does not type check, because it constructs the program as a syntax tree and then renders it.

The x/tools dependency is what lets the generator reason about types rather than guess them. It loads your package, resolves the interface, and knows what every field's type actually is, including through named types and across package boundaries. That is why the generator can tell you that two types cannot be converted without losing information, and it is also why the tool needs to be run against real code rather than against a description of it.

The repository structure mirrors that. There is a builder directory, a generator directory, a method directory, an namer directory, a pkgload directory, an xtype directory for the type model, an enum directory, a config directory for the settings, a comments directory, a cli directory and a cmd directory for the entry point, plus runner.go at the top level. The docs directory holds the documentation that the site is built from, and the example directory holds the per-feature example programs.

The project is a code generator, so it has to be run, and the question of when you run it is the one decision that shapes everything else about adopting it.

The generator has to run somewhere, and that is the cost

Unlike a library, a code generator does not disappear into your build. It is a program that has to be executed, and where you execute it determines how much friction it adds to a project.

The documentation links a Getting Started guide, an Installation page, a CLI reference and a Config reference. The configuration is YAML, and the settings cover how the generator resolves packages, what it does with names, and how it handles errors. The CLI reference page means there are flags and subcommands beyond the plain run, and the documentation does not reproduce them in the README, so you would read the site for that.

The realistic options are three. You run the generator yourself and commit the result, which is the most common arrangement and the one that matches the example output, since the generated file lives in its own generated package and the README example imports it under that name. You wire it into a go:generate directive so a developer regenerates on demand. Or you run it in CI and fail the build when the committed output differs from what the generator produces, which is the option that keeps the output honest but requires a CI step to compare the two.

The third option is the one that pays for the setup. If the generated code can drift from the interface, then the compiler is checking against code that may not be what the generator would produce, and you have lost the main benefit of the tool. The generator's own value, that the interface is the source of truth and the code follows, only holds if regeneration is part of the normal workflow.

The go.mod requirement of Go 1.26.0 is also worth noting for adoption, because it means the tool needs a current toolchain, and the generated code is emitted against whatever the current language version supports. That is not a problem for a new project, and it is a compatibility consideration for one that has to build with an older Go.

The final piece of the picture is the licence and the release record. The project is MIT licensed with the file in the repository, and the documentation says goverter uses SemVer for versioning the CLI. The recent releases are v1.11.1 on 2026-10-10, v1.11.0 on 2026-09-11 and v1.10.0 on 2026-08-25, and the last push to the repository was on 2026-10-10.

Where goverter is the wrong choice

Three situations rule it out.

The first is a small codebase with a handful of mappings. If you have three small structs and one conversion function, a code generator is more machinery than the problem. The generated file will be longer than the hand written function it replaces, and the reader has to learn a new tool to understand a file that used to be self evident. The break-even point is somewhere around the point where you start forgetting to update a field.

The second is run time conversion of types that are not known at build time. That is the case reflection exists to serve, and goverter is explicitly not that tool. If your application deserialises a payload into a map and then needs to move it into a struct at run time, with the target type selected by a configuration value, a generator cannot help because there is no interface to point at. A library like jinzhu/copier, which the documentation names as the alternative being replaced, exists for exactly that case, and it is not wrong to use it here.

The third is a team that does not want generated code in the repository. Some projects have a policy against checked-in generated files, and some prefer to keep the source of truth in a form a non-Go developer can read. The interface with its marker comments is readable, but it is still a Go file with Go syntax, and the mapping rules live in comments, which some reviewers find harder to work with than a table in a config file or a spreadsheet.

There is also a narrower point about what a converter can do. The value of goverter is that it fails loudly on a mismatch, and that only covers the mappings you express. It will not tell you that your domain model and your API model have drifted apart conceptually, only that a particular field has no source. The generator checks the type level, not the intent level, and a reviewer still has to read the interface to know whether the mapping is right.

So the question to ask is not whether goverter is well made but whether your project has a mapping problem big enough to need a dedicated tool, and whether the answer is worth a build step.

Editorial conclusion

goverter fits a Go codebase that keeps a domain model and an API model as separate types and has grown tired of hand written conversion functions that silently drop fields, because the compiler becomes the thing that tells you a field was forgotten. It is the wrong choice for a codebase with a handful of small mappings, where the generated file is more code than the conversion itself, and it is the wrong choice if you need conversion at run time on values whose types are not known when you build, since that is the case reflection exists to serve. Before adopting it, run the generator with your real structs and read the errors it produces, because the value of the tool is entirely in how clearly it reports a mapping it cannot do, and decide early whether the generated files are committed to the repository or produced in CI, since that choice determines who reviews them.

Frequently asked questions

What is goverter and how do you use it?

It is a tool for creating type-safe converters in Go, and all you have to do is create an interface and execute goverter. The methods on a goverter:converter interface become the generated conversion functions, and the generated code is placed in its own package.

How is goverter different from reflection-based copying libraries?

The project is meant as an alternative to jinzhu/copier that does not use reflection. The generated code assigns fields directly, so there is no reflection or type assertion at run time, and the generated output is checked by the compiler.

What errors does goverter report at generation time?

The documentation names two cases with clear errors: the target struct has unmapped fields, and types cannot be converted without losing information. The features list also notes that deep copies are the default with shallow copying supported, and that enum types and custom converter methods are supported.

What are the requirements for using goverter?

The module requires Go 1.26.0 and depends on dave/jennifer for code generation, golang.org/x/tools for package loading and type checking, gopkg.in/yaml.v3 for configuration and stretchr/testify for tests. The project is MIT licensed and versions the CLI with SemVer.

Is goverter still being developed?

The last push to the repository was on 2026-10-10 and the repository is not archived. The recent releases are v1.11.1 on 2026-10-10, v1.11.0 on 2026-09-11 and v1.10.0 on 2026-08-25, and the documentation site is at goverter.jmattheis.de.

Official sources

  1. jmattheis/goverter on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jmattheis-goverter.svg)](https://hysenlabs.com/projects/jmattheis-goverter)

Was this useful?