Coder Social home page Coder Social logo

jmattheis / goverter Goto Github PK

View Code? Open in Web Editor NEW
448.0 7.0 42.0 704 KB

Generate type-safe Go converters by simply defining an interface

Home Page: https://goverter.jmattheis.de/

License: MIT License

Go 99.96% Shell 0.04%
golang go converter struct generator code-generation copy

goverter's Introduction

goverter

a "type-safe Go converter" generator

Build Status codecov Go Report Card Go Reference latest release

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.

Getting StartedInstallationCLIConfig

Features

Example

Given this converter:

package example

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

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

type Input struct {
    Name string
    Nested InputNested
}
type InputNested struct {
    AgeInYears int
}
type Output struct {
    Name string
    Age int
    Irrelevant bool
}

Goverter will generated these conversion methods:

package generated

import example "goverter/example"

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source example.Input) example.Output {
    var exampleOutput example.Output
    exampleOutput.Name = source.Name
    exampleOutput.Age = source.Nested.AgeInYears
    return exampleOutput
}
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
}

See Getting Started.

Versioning

goverter uses SemVer for versioning the cli.

License

This project is licensed under the MIT License - see the LICENSE file for details

Logo by MariaLetta

goverter's People

Contributors

antonyc avatar ewhauser avatar francois-du-toit-hpe avatar jmattheis avatar nissim-natanov-outreach avatar pavelpatrin avatar sergeydobrodey avatar soniah avatar vvasystem avatar yasserrabee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

goverter's Issues

Proposal: generate conversion function by defining a type

Is your feature request related to a problem? Please describe.
Currently we have to define an interface before generating the code:

// goverter:converter
type Converter interface {
  Convert(source []Input) []Output
}

And the usage code looks like:

output := &ConverterImpl{}.Convert(input)

It's cool, but a little bit uncommon, because the &ConverterImpl{} looks redundant.

Describe the solution you'd like
We can generate conversion function by defining a type.

For example, we define a type:

// goverter:name Convert
type _ func(source []Input) []Output

This definition gives the tool enough information to generate the code.
The generated code looks like:

func Convert(source []Input) []Output{
//.....
}

We can invoke the generated function without the redundant &ConverterImpl{} :

output := Convert(input)

@v0.17.1 report this error with go run :# github.com/jmattheis/goverter/xtype ../../../../pkg/mod/github.com/jmattheis/[email protected]/xtype/type.go:279:11: t.TypeArgs undefined (type *types.Named has no field or method TypeArgs) make: *** [model_convert] Error 2

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Include the input file that goverter has problems with, and describe the steps
you took to trigger the bug

Expected behavior
A clear and concise description of what you expected to happen.

Generator throws an error when using inheritance and source/target as pointers

Describe the bug
Generator throws an error when using inheritance and source/target as pointers.

To Reproduce
This generates a correct code

// goverter:converter
type Converter interface {
	// goverter:mapIdentity Base
	Convert(in In) Out
}

type In struct {
	Name string
	Age  int
}

type Out struct {
	Base
	Age int
}

type Base struct {
	Name string
}

But this throws an error

// goverter:converter
type Converter interface {
	// goverter:mapIdentity Base
	Convert(in *In) *Out
}

type In struct {
	Name string
	Age  int
}

type Out struct {
	Base
	Age int
}

type Base struct {
	Name string
}
Error while creating converter method:
    func (mappers.Converter).Convert(in *mappers.In) *mappers.Out

| *mappers.In
|
|     | mappers.In
|     |
|     |
|     |
source*.???
target*.Base
|     | |
|     | | mappers.Base
|     |
|     | mappers.Out
|
| *mappers.Out

Cannot match the target field with the source entry: "Base" does not exist.
exit status 1

Expected behavior
I would expect it to correctly generate a converter, but maybe I'm missing something.

Support nested anonymous inherited structs

Is your feature request related to a problem? Please describe.
Currently there is no way to convert the input to output from this example:

type Input struct {
	Hello string
	World int
}

type A struct {
	Hello string
}

type B struct {
	A
	World int
}

type Output struct {
	B
}

Describe the solution you'd like
Being able to add a dot . separator for anonymous inherited structs via goverter:mapIdentity (like goverter:map for regular fields) would make this possible:

// goverter:converter
type Converter interface {
	// goverter:mapIdentity B B.A
	ConvertBase(source Input) Output
}

Describe alternatives you've considered
You can resolve a single depth nested struct using this method, but it will error that it is unable to map the identity to the A struct:

// goverter:converter
type Converter interface {
	// goverter:mapIdentity B
	ConvertBase(source Input) Output
}
Error while creating converter method:
    func (db/internal/types.Converter).ConvertBase(source db/internal/types.Input) db/internal/types.Output

| db/internal/types.Input
|
|      | db/internal/types.Input
|      |
|      |
|      |
source.<mapIdentity>.???
target.B            .A
|      |             |
|      |             | db/internal/types.A
|      |
|      | db/internal/types.B
|
| db/internal/types.Output

Cannot set value for field A because it does not exist on the source entry.

Generate functions directly without Converter interface

Goverter generates an implementation for a user defined interface with conversion methods. The usability of that could be better, because now the user has to initialize the Converter struct and call methods from there. Goverter could generate functions directly. If this ticket will be included into Goverter, then the old interface approach will most likely be removed.

Current example (click me)

// goverter:converter
// goverter:extend ConvertAnimals
type Converter interface {
    Convert(source Input) Output

    // used only in extend method
    ConvertDogs([]Dog) []Animal
    ConvertCats([]Cat) []Animal
}

type Input struct {
    Animals InputAnimals
}
type InputAnimals struct {
    Cats []Cat
    Dogs []Dog
}
type Output struct {
    Animals []Animal
}

type Cat struct { Name string }
type Dog struct { Name string }

type Animal struct { Name string }

func ConvertAnimals(c Converter, input InputAnimals) []Animal {
    dogs := c.ConvertDogs(input.Dogs)
    cats := c.ConvertCats(input.Cats)
    return append(dogs, cats...)
}

Proposed approach

// goverter:func
// goverter:map Animals | CombineAnimals
var ConvertInput func (source *Input) *Output

// goverter:func
var ConvertDogs func([]Dog) []Animal

// goverter:func
var ConvertCats func([]Cat) []Animal

func CombineAnimals(input Input) []Animal {
	dogs := ConvertDogs(input.Dogs)
	cats := ConvertCats(input.Cats)
	return append(dogs, cats...)
}

type Input struct {
	Cats []Cat
	Dogs []Dog
}
type Output struct { Animals []Animal }
type Cat struct{ Name string }
type Dog struct{ Name string }
type Animal struct{ Name string }

Variables marked with goverter:func would allow all configs that can be added to the conversion methods on interfaces. Global goverter flags like goverter:extend could be configured on the package declaration.

When executing goverter on this input file, it could generate an init() method and set the generated implementation. The generated code for the example above could look like this:

Generated Code Example (click me)

func init() {
	ConvertInput = func(source *Input) *Output {
		var pStructsOutput *Output
		if source != nil {
			var structsOutput Output
			structsOutput.Animals = CombineAnimals((*source))
			pStructsOutput = &structsOutput
		}
		return pStructsOutput
	}

	ConvertCats = func(source []Cat) []Animal {
		var structsAnimalList []Animal
		if source != nil {
			structsAnimalList = make([]Animal, len(source))
			for i := 0; i < len(source); i++ {
				structsAnimalList[i] = structsCatToStructsAnimal(source[i])
			}
		}
		return structsAnimalList
	}

	ConvertDogs = func(source []Dog) []Animal {
		var structsAnimalList []Animal
		if source != nil {
			structsAnimalList = make([]Animal, len(source))
			for i := 0; i < len(source); i++ {
				structsAnimalList[i] = structsDogToStructsAnimal(source[i])
			}
		}
		return structsAnimalList
	}
}

func structsCatToStructsAnimal(source Cat) Animal {
	var structsAnimal Animal
	structsAnimal.Name = source.Name
	return structsAnimal
}
func structsDogToStructsAnimal(source Dog) Animal {
	var structsAnimal Animal
	structsAnimal.Name = source.Name
	return structsAnimal
}

Testable version available: #77 (comment)

Please 👍 this issue if you want this functionality. If you have a specific use-case in mind, feel free to comment it.

Passthrough additional data to the conversion methods

I have a Job model, it has set of Task IDs stored in []int field.

I want to calculate some task statistics in converter extension function using preloaded set of Tasks, but I have access only ti Job instance. Can I somehow passthrough preloaded tasks to use them while converting the job?

E.g., we can get them from the context, or another passed object or map.

// goverter:extend JobTaskStatsToPb
type Converter interface {
    JobToPb(context.Context, models.Job) taskspb.Job
}

func JobTaskStatsToPb(ctx context.Context, value models.JobTaskStats) taskspb.JobTaskStats {
    jobTaskIDs := value.TaskIDs
    tasksData := ctx.Value("TasksStorageCtxKey").(map[int]models.Task)
    ... use tasks data from context to calculate job task stats ...
    return calculatedJobTaskStats
}

Improve value return on err

With #18 a compile error was fixed, but not in the most optimal way, because it produces an unnecessary allocation in the error case.

func (c *ConverterImpl) Convert(source execution.Input) (execution.Output, error) {
var structsOutput execution.Output
xint, err := execution.StringToZeroConvert(source.Age)
if err != nil {
var errValue execution.Output
return errValue, err
}
structsOutput.Age = xint
structsOutput.Datum = execution.IntToStringConvert(source.Datum)
return structsOutput, nil
}

Currently, goverter doesn't know if there is already a variable existing with the right type, maybe there is a smart way to do this?

Support Interface{} (any) type conversion

Is your feature request related to a problem? Please describe.
I have several structs that are generated with a type of interface{}. Converting an interface to an interface is unsupported

Describe the solution you'd like
Using the example below, allow goverter to convert a struct that has an interface to another struct that has the same interface type

package advanced

// goverter:converter
type ConverterInterface interface {
	ConvertHouse(source DBHouseNamesI) APIHouseNamesI
}

type DBHouseNamesI struct {
	Name any
}

type APIHouseNamesI struct {
	Name any
}

Describe alternatives you've considered
Try to get away from a type any, but that is a limitation of other code generations.

How to generate output in same namespace as config?

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.

Describe your question
I have an situation that is maybe not-idiomatic, but we have some unique constraints.

I need to generate files in the same directory/namespace as the goverter config. The file gets generated, but the generated file tries to import the current namespace, which obviously doesn't work.

Is there some config/flag I can get goverter to detect it's working with a single namespace, and doesn't need to import itself?

I forked goverter and mangled the runner_test.go and added a scenario that generates output in the same package as the imported models/extended methods, and it worked... which leads me to believe something in application bootstrapping is just assuming a namespace must always be imported?

File structure is as follows:

public-api/pkg/button/goverter_input.go
public-api/pkg/button/some_models.go
public-api/internal/service/models/more_models.go

and I want to generate to:

public-api/pkg/button/converter_gen.go

Error is as follows:

-: import cycle not allowed: import stack: [github.com/morganhein/public-api/pkg/button github.com/morganhein/public-api/pkg/button]
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package button

import (
	models "github.com/morganhein/public-api/internal/service/models"
	"github.com/morganhein/public-api/pkg/button"    // <--- don't need this, and references here should not require a namespace
)

wrap custom convertion

Is your feature request related to a problem? Please describe.


type Input struct {
  V1, V2 float64
}

type Output [2]float64

func Custom(in Input) Output {
  ...
}

type Converter interface {
  Output(Input) Output
}

Describe the solution you'd like
The generated code be like:

type Converter struct {}
...
func (c *Converter) Output(source Input) Output {
  return Custom(source)
}

Right now goverter seems to not find this custom and throws error that it could't convert those two values.
Describe alternatives you've considered
For sure I could use my custom function directly but I would prefer to not mix custom functions with converter's method calls.

[Questions] When generating converters, is it possible to generate constructor at the same time

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

type ResourceConverterImpl struct{}

func NewResourceConverter() gen.ResourceConverter {
	return &ResourceConverterImpl{}
}

func (c *ResourceConverterImpl) ConvertModelToDomain(source *model.Resource) (*types.Resource, error) {
	var pTypesResource *types.Resource
	if source != nil {
		typesResource := c.modelResourceToTypesResource(*source)
		pTypesResource = &typesResource
	}
	return pTypesResource, nil
}

It is a good choice for dependency injection or initialization declaration

[Questions] Does the generated code need optimization?

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

type Tx int

type In struct {
	A Tx
}
type Out struct {
	A Tx
}

// goverter:converter
type Converter interface {
	Convert(in constructor.In) (constructor.Out, error)
	ConvertList(in []constructor.In) ([]constructor.Out, error)
}

result: generated.go

// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package generated

import (
	"fmt"
	constructor "xxx/constructor"
)

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source constructor.In) (constructor.Out, error) {
	var constructorOut constructor.Out
	constructorOut.A = constructor.Tx(source.A)
	return constructorOut, nil
}
func (c *ConverterImpl) ConvertList(source []constructor.In) ([]constructor.Out, error) {
	var constructorOutList []constructor.Out
	if source != nil {
		constructorOutList = make([]constructor.Out, len(source))
		for i := 0; i < len(source); i++ {
			constructorOut, err := c.Convert(source[i])
			if err != nil {
				var errValue []constructor.Out
				return errValue, fmt.Errorf("error setting index %d: %w", i, err)
			}
			constructorOutList[i] = constructorOut
		}
	}
	return constructorOutList, nil
}
  1. The field type is related, they are all Tx, just assign directly? No forced conversion required?
  2. If it is a slice, len(source) is used twice, use the variable to receive, reduce the number of calls?

How to avoid `go vet copylocks` warnings?

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

How to avoid go vet copylocks warnings?

e.g.

// goverter:converter
type Converter interface {
	Convert(source []Input) []Output
}

// generated gRPC code
type Input struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

type Output struct {
	Name string
}
goverter -ignoreUnexportedFields ./example/simple

generated.go
image

call of ***** copies lock value: ***** contains google.golang.org/protobuf/internal/impl.MessageState contains sync.Mutex

I want to use pointer.
image

return error from mapExtend func

Is your feature request related to a problem? Please describe.

We use mapExtend to map two fields from the source type to a single field in the target. Applying this mapping can return an error and it should be propagated back to the main convert method.

goverter prohibits returning multiple values from mapExtend functions with error xx has no or too many returns

Describe the solution you'd like
A clear and concise description of what you want to happen.

Support returning errors the same as goverter:extend

// goverter:converter
type Converter interface {
    // goverter:mapExtend FullName ExtendFullName
    Convert(source Input) (Output, error)
}

type Input struct {
    ID int
    FirstName string
    LastName string
}
type Output struct {
    ID int
    FullName string
}
func ExtendFullName(source Input) (string, error) {
    // .... some crazy parsing logic that can return an error
    if err != nil {
        return "", nil
    }
    return source.FirstName + " " + source.LastName, nil
}

Describe alternatives you've considered

We didn't find a possible alternative to use goverter in that scenario. We tried multiple solutions that all do the parsing outside goverter. While it was possible, we missed the best part of having everything generated automatically.

Convert pointer to not pointer and reverse

Is your feature request related to a problem? Please describe.

Today, goverter can return the following error:

TypeMismatch: Cannot convert *float64 to float64

And this can be similar also for structure.

Describe the solution you'd like

I would believe if the type kind is the same, goverter should try to convert:

  1. if ptr is nil return default value
  2. convert pointed value

Generated file doesn't contain generic type

Describe the bug
Generated file dosn't contain generic type.

To Reproduce

  • Run go generate with this snippet:
package mapper

//go:generate goverter -ignoreUnexportedFields -packageName mapper -output mapper2.gen.go ./

// goverter:converter
// goverter:extend github.com/gotidy/ptr:To.+
type Mapper interface {
	// goverter:autoMap Content
	MapTypedToAnother(Typed[InnerType]) ResultType[AnotherInnerType]
}

type Typed[S any] struct {
	ID      string
	Content *S
}

type InnerType struct {
	Name string
}

type AnotherInnerType struct {
	Name string
}

type ResultType[S any] struct {
	ID      string
	Name    string
	Content *S
}
  • Currently (v0.17.0) this is actual behavior:
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package mapper

import (
	ptr "github.com/gotidy/ptr"
)

type MapperImpl struct{}

func (c *MapperImpl) MapTypedToAnother(source Typed) ResultType {
	var mapperResultType ResultType
	mapperResultType.ID = source.ID
	var pString *string
	if source.Content != nil {
		pString = &source.Content.Name
	}
	mapperResultType.Name = ptr.ToString(pString)
	mapperResultType.Content = c.pMapperInnerTypeToPMapperAnotherInnerType(source.Content)
	return mapperResultType
}
func (c *MapperImpl) pMapperInnerTypeToPMapperAnotherInnerType(source *InnerType) *AnotherInnerType {
	var pMapperAnotherInnerType *AnotherInnerType
	if source != nil {
		var mapperAnotherInnerType AnotherInnerType
		mapperAnotherInnerType.Name = (*source).Name
		pMapperAnotherInnerType = &mapperAnotherInnerType
	}
	return pMapperAnotherInnerType
}

Expected behavior

  • Typed should be Typed[InnerType] and ResultType should be ResultType[AnotherInnerType]
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package mapper

import (
	ptr "github.com/gotidy/ptr"
)

type MapperImpl struct{}

func (c *MapperImpl) MapTypedToAnother(source Typed[InnerType]) ResultType[AnotherInnerType] {
	var mapperResultType ResultType[AnotherInnerType]
	mapperResultType.ID = source.ID
	var pString *string
	if source.Content != nil {
		pString = &source.Content.Name
	}
	mapperResultType.Name = ptr.ToString(pString)
	mapperResultType.Content = c.pMapperInnerTypeToPMapperAnotherInnerType(source.Content)
	return mapperResultType
}
func (c *MapperImpl) pMapperInnerTypeToPMapperAnotherInnerType(source *InnerType) *AnotherInnerType {
	var pMapperAnotherInnerType *AnotherInnerType
	if source != nil {
		var mapperAnotherInnerType AnotherInnerType
		mapperAnotherInnerType.Name = (*source).Name
		pMapperAnotherInnerType = &mapperAnotherInnerType
	}
	return pMapperAnotherInnerType
}

This package is AMAZING

MARRY ME.

No srsly, this package is the missing piece in my stack to keep type safety across layers, no more gross JSON marshalling!

WILL SPONSOR SOON. THANK YOU SO MUCH!

Allow goverter to consider underlying types when mapping named types

Is your feature request related to a problem? Please describe.

We currently use Goverter to do a lot of conversion from Protocol Buffers to sqlc. The biggest annoyance we have is that we have to write conversion methods for every enum, a la:

func ProgramStatusEnumToProgramStatus(v gensql.ProgramStatusEnum) pb.Program_ProgramStatus {
	if v == "" {
		return pb.Program_ProgramStatus(0)
	}
	return pb.Program_ProgramStatus(Program_ProgramStatus_value[string(v)])
}

We wrote a Protocol Buffer compiler plugin to start automatically generating methods to convert enums to/from strings to make integration with goverter easier:

func ProgramStatusEnumToProgramStatus(v gensql.ProgramStatusEnum) pb.Program_ProgramStatus {
	return pb.ConvertStringToProgram_ProgramStatus(string(v))
}

However, we still have to write these extend methods because Goverter doesn't recognize that the underlying type of gensql.ProgramStatusEnum is a string.

Describe the solution you'd like

I think Goverter's default behavior is totally reasonable here - just use the named type and don't look at the underlying type. However, in this case it would remove a lot of pain for our team if goverter would consider the underlying type when doing the conversion mappings as we already have converter methods available. I imagine this would need to be opt-in, otherwise it could break existing users.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

We could try to code generate functions to convert our Protocol Buffers to sqlc models directly, but unfortunately that would require a ton of additional mapping. Alternatively, we could hack sqlc to not use named types for enums but they are often useful.

Pointer to slice generates invalid code

Describe the bug
When a field of a struct contains a pointer to a slice, ie *[], it generates invalid dereference code.

To Reproduce

//go:generate go run github.com/jmattheis/goverter/cmd/goverter ...

package converter

// goverter:converter
type Converter interface {
   ConvertHouse(source DBHouseNames) APIHouseNames
}

type DBHouseNames struct {
   Names *[]string
}

type APIHouseNames struct {
   Names *[]string
}

This will end up generating code that contains invalid dereferences

...
*source.Names[i]

Expected behavior
it should generate code that properly dereferences the array before attempting to use the index

(*source.Names)[i]

[Questions] When generating files, is it possible to generate independent files for each struct ?

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

Now it seems that all defined interface generation structures will be in the same file generated/generated.go, but if it is developed by multiple people, my partner and I will define different interface generators, which will easily cause Git conflicts. If each structure can be generated independently file that's great

Embedded structs

I have the following scenario:

package example

// goverter:converter
type Converter interface {
	ConvertPerson(source *Person) *APIPerson
}

type Address struct {
	Street string
	City   string
}

type Person struct {
	*Address

	Name string
}

type APIPerson struct {
	Name   string
	Street string
	City   string
}

How can I use goverter to map from a Person to an APIPerson?

I am facing two problems:

  1. Mapping pointer values to non-pointer values
  2. Manually listing all embedded fields

For (1) ideally I would like the target values to either be ignored or set to zero values if the source is nil.
For (2) can I somehow map the fields in goverter without having to list them out one by one? I played with mapIdentity but it wasn't clear from the README if this applies to my situation, since it seems like the reverse.

Thanks in advance for your help.

Passing Converter interface to mapExtend ?

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
I'm wondering why there isn't the possibility to ask for the Converter interface when using mapExtend ?

Taking the documentation exemple, this would give something like this :

// goverter:converter
type Converter interface {
    // goverter:mapExtend FullName ExtendFullName
    // goverter:mapExtend Age DefaultAge
    Convert(source Input) Output
}

type Input struct {
    ID int
    FirstName string
    LastName string
}
type Output struct {
    ID int
    FullName string
    Age int
}
func ExtendFullName(c Converter, source Input) string {
    return source.FirstName + " " + source.LastName
}
func DefaultAge() int { return 42 }

This would perfectly solve my use case, but maybe there's another solution :

// goverter:converter
type Converter interface {
    // goverter:mapExtend Car ExtendCar
    Convert(source Input) Output

    ConvertBmwToCar(source *Bmw) *outputBmw
    ConvertFerrariToCar(source *Ferrari) *outputFerrari
}

func ExtendCar(input *Input) []Car {
    // I need Converter here so that I can loop on Bmw and Ferrari's array of the Input struct and
    // call c.ConvertBmwToCar(input.Bmw[0]) || c.ConvertFerrariToCar(input.Ferrari[0])
}

type Input struct {
	Bmw    []*Bmw
	Ferrari []*Ferrari
}

type Car interface{}

// implement Car
type outputBmw struct{}

// implement Car
type outputFerrari struct{}

type Output {
      Car []Car
}

Global option for ignoring unexported fields

Is your feature request related to a problem? Please describe.

I'm using with Protocol Buffers which have 4 unexported fields. I can ignore these via:

	// goverter:ignore size
	// goverter:ignore sizeCache
	// goverter:ignore state
	// goverter:ignore unknownFields

on each conversion method but it is tedious and duplicative.

Describe the solution you'd like
A global option for ignoring this on all objects would be great or an option on each method.

Describe alternatives you've considered
Currently, just ignoring on each method.

Is there a way to convert to code generate by protobuf oneof

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
I'm trying to convert a pure Go struct to a struct generated using protobuf that include oneof. The generated code for oneof create an un-exported interface ref with multiple struct that implement the interface. I didn't find a way to use goverter to convert such a field. I tried using a custom Mapping Method but since the type is not exported by protobuf, it's not possible to define such method. One workaround to work with oneof is to create a type alias in the same module as the generated code. However, goverter match type based on there name ref so an alias wouldn't work. Is there a way that I missed to convert a struct that contains such a oneof?

Unable to convert pointers to direct types like *struct => struct

Is your feature request related to a problem? Please describe.
I'm trying to convert values from *struct to struct automatically, because my models are value based, but gRPC messages are always pointer based. It requires me to write a lot of intermediate converters and next enable them with extend command. Moreover, conversions from struct to *struct also not working as expected, because they don't use field settings for inner method (bug?).

Describe the solution you'd like
I want to be able to use the following syntax

type Converter interface {
    FromGrpcMessage(*grpc.Message) domain.Model
}

I already implemented this feature and tried to fix a bug with ignoring original method tags (should be discussed, see PR).

Is it possible to set to automatically ignore non-existent fields

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

type In struct {
	Name      string
	Age       int
	OtherMiss string
}

type Out struct {
	Id       int
	Name     string
	Age      int
	Password string
}

// goverter:converter
type Converter interface {
	// InToOut 转换 *In 到 *Out
	// goverter:ignoreMissSource
	// goverter:ignore OtherMiss
	InToOut(source *In) (*Out, error)
	// InToOut2 转换 *Out 到 *In
	// goverter:ignoreMissTarget
	// goverter:ignore Id Password
	InToOut2(source *In) (*Out, error)
	// InToOut3 转换 *In 到 *Out
	// goverter:ignoreMissAll
	InToOut3(source *In) (*Out, error)
	// InToOut4 转换 *In 到 *Out
	// goverter:only Name
	InToOut4(source *In) (*Out, error)
}

In the above conversion, I only want to convert Age Name

It is recommended to add 4 annotations

  1. ignoreMissSource Indicates ignoring fields missing from the source relative to the target, including: Out.Id Out.Password
    So InToOut() needs to add goverter:ignore OtherMiss
  2. ignoreMissTarget Indicates to ignore fields missing from the target relative to the source, including: In.OtherMiss
    So InToOut2() needs to add goverter:ignore Id goverter:ignore Password
  3. ignoreMissAll Indicates ignoring fields that do not match on both sides, including: In.OtherMiss Out.Id Out.Password
    So InToOut3() does not need to add the goverter:ignore annotation
  4. goverter:only Indicates that only the selected fields will be converted, other fields will be ignored
    So InToOut4 only: Name will be converted, other fields will be ignored

This will greatly reduce the workload of writing goverter:ignore annotations, which is very helpful to improve efficiency

TypeMismatch: Cannot convert interface{} to interface{}

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
I have three layers: database, service and api

For the convertor from database -> service I am using the following custom function for json.RawMessages (empty by default JSONB columns):

func RawMessageToMapStringInterface(value json.RawMessage) map[string]interface{} {
	var res map[string]interface{}
	err := json.Unmarshal(value, &res)
	if err != nil {
		fmt.Println("Error unmarshal")
		return nil
	}
	return res
}

This fixes a panic panic: unsupported type interface{}

After that, I try to convert service layer to api layer but get error:

Error while creating converter method:
    func (/converter.ServiceToAPIConverter).ServiceToApi(source serviceStruct) apiStruct

|  service.User
|
|      | map[string]interface{}
|      |
|      |       | <mapvalue> interface{}
|      |       |
source.Settings[]
target.Settings[]
|      |       |
|      |       | <mapvalue> interface{}
|      |
|      | map[string]interface{}
|
| api.User

TypeMismatch: Cannot convert interface{} to interface{}

Any help ?

Structure conversion error

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Include the input file that goverter has problems with, and describe the steps
you took to trigger the bug

Expected behavior
A clear and concise description of what you expected to happen.

time.go

import "github.com/golang-module/carbon/v2"

type In struct {
	CreatedAt  carbon.DateTime
	ReleasedAt *carbon.DateTime
}

type Out struct {
	CreatedAt  carbon.DateTime
	ReleasedAt *carbon.DateTime
}

// goverter:converter
type Converter interface {
	ModelToDomain(source *In) (*Out, error)
}
  1. If the unexported field is ignored, panic: runtime error: invalid memory address or nil pointer dereference
go run github.com/jmattheis/goverter/cmd/goverter --ignoreUnexportedFields --wrapErrors ./time.go
  1. If unexported fields are not ignored, an error field mismatch will be reported
go run github.com/jmattheis/goverter/cmd/goverter  --wrapErrors ./time.go

[Questions] The underlying data types are consistent, can they be converted?

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

greeter.go

type Greeter struct {
    Status      int32
}

the greeter.pb.go generated by the protobuf file

type GreeterStatus int32

type GreeterSaveBodyContext struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Status GreeterStatus `protobuf:"varint,1,opt,name=status,proto3,enum=greeter.v1.GreeterStatus" json:"status,omitempty"`
}

this fails to generate code

Allow external methods in goverter:mapExtend

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

I tried importing packages, splitting packages and functions with : or . to no avail

// goverter:converter
type Converter interface {
    // goverter:mapExtend FullName github.com/jmattheis/goverter.ExtendFullName
    // goverter:mapExtend Age github.com/jmattheis/goverter:ExtendFullAge
    Convert(source Input) Output
}

mapping consts

Hi Jannis, this is part question, part feature request, part "should I write this?".

If my target field should be set to a const, how would I do that? For example:

       const ageOfConsent = 18 
   
        type Input struct {
            Name string
        }

        type Output struct {
            Name2 string
            Age int
        }

Desired field mappings:

Input.Name -> OutPut.Name2
ageOfConsent -> Output.Age

I was wondering if I should attempt to write this (as a feature), but I'd be interested in your ideas. Here's a possible syntax:

        // goverter:converter
        type Converter interface {
            // goverter:const ageOfConsent:Age
            Convert(source Input) Output
        }

missing & in generated code

Describe the bug
Generation doesn't throw any error and successfully generates invalid code.

To Reproduce

// goverter:converter
type Converter interface {
	// goverter:mapIdentity Address
	ConvertPerson(source Person) (APIPerson, error)

	// goverter:map Name StreetName
	ConvertAddress(source Person) (APIAddress, error)
}

type Person struct {
	Name   string
	Street string
	City   string
}

type APIPerson struct {
	Name    string
	Address *APIAddress
}

type APIAddress struct {
	StreetName string
	City       string
}

Expected behavior
The generated code is missing ampersand sign.

type ConverterImpl struct{}

func (c *ConverterImpl) ConvertAddress(source proto.Person) (proto.APIAddress, error) {
	var protoAPIAddress proto.APIAddress
	protoAPIAddress.StreetName = source.Name
	protoAPIAddress.City = source.City
	return protoAPIAddress, nil
}
func (c *ConverterImpl) ConvertPerson(source proto.Person) (proto.APIPerson, error) {
	var protoAPIPerson proto.APIPerson
	protoAPIPerson.Name = source.Name
	pProtoAPIAddress, err := c.protoPersonToPProtoAPIAddress(source)
	if err != nil {
		return protoAPIPerson, err
	}
	protoAPIPerson.Address = pProtoAPIAddress
	return protoAPIPerson, nil
}
func (c *ConverterImpl) protoPersonToPProtoAPIAddress(source proto.Person) (*proto.APIAddress, error) {
	protoAPIAddress, err := c.ConvertAddress(source)
	if err != nil {
		return protoAPIAddress, err
	}
	return &protoAPIAddress, nil
}

protoPersonToPProtoAPIAddress in case of err != nil in returned value & is missing. Btw. why there's doubled P in method's name.

Mapping context values

Goverter should allow passing a mapping context through converter methods for custom conversions. This allows more customizability when the conversion requires other state. See also https://mapstruct.org/documentation/stable/api/org/mapstruct/Context.html

Example:

// goverter:converter
// goverter:extend LookupUser
type Converter interface {
    Convert(source Input, context UserContext) (Output, error)
}

type UserContext struct {
    UserNames map[int]string
}

func LookupUser(userID int, context UserContext) (User, error) {
    user := User{ID: userID}
    name, ok := context.UserNames[userID]
    if !ok {
        return user, fmt.Errorf("user with id %d not found", userID)
    }
    user.Name = name
    return user, nil
}

type Input struct {  UserIDs []int }
type Output struct { Users []User  }
type User struct {
    ID int
    Name string
}

Conversion contexts are additional parameters that are passed into the conversion methods (See Convert method from Converter), and they should be passed into custom methods (See LookupUser).

Please 👍 this issue if you like this functionality. If you have a specific use-case in mind, feel free to comment it.

Strict enum support

It would be cool if goverter could strictly convert named types / enums.

// goverter:converter
type Converter interface {
    Convert(InputColor) OutputColor
}

type InputColor int
const (
    InputGreen InputColor = iota
    InputBlue
    InputRed
)

type OutputColor string
const (
    OutputGreen OutputColor = "green"
    OutputBlue  OutputColor = "blue"
    OutputRed   OutputColor = "red"
)

Goverter should fail if the target enum is missing options from the source type.

Currently, goverter just converts the underlying value for named types. So a named string type will be converted to another named string type, without checking if the value is valid.

Unknowns:

Please 👍 this issue if you like this functionality

Unable to specify matchIgnoreCase at CLI args and Interface comment

Is your feature request related to a problem? Please describe.
I'm converting gRPC messages to domain models. My domain models has ID field in upper case everywhere, but gRPC generated code uses Id name. It relates to all converter methods, but I can't specify matchIgnoreCase on interface level.

Describe the solution you'd like
I want to specify matchIgnoreCase option for entire interface or via CLI.

Describe alternatives you've considered
Right now I'm specifying this tag for every method, but is is not really convenient.

interface level option inheritance

Let's consider example based on code generated by grpc that adds 3 additional fields state, sizeCache and unknownFields to every generated structure. In case when I've defined interface that will translate multiple structures I have to repeat myself on every structure to ignore those fields and additionally matchIgnoreCase. It would be cool to be able to define ignore and matchIgnoreCase at the level of interface so that every method has them in common defined.

Instead of:

type Transformer interface {
 //goverter:matchIgnoreCase
 //goverter:ignore state
 //goverter:ignore sizeCache
 //goverter:ignore unknownFields
 Value(in.Struct1) out.Struct1
 //goverter:matchIgnoreCase
 //goverter:ignore state
 //goverter:ignore sizeCache
 //goverter:ignore unknownFields
 Value(in.Struct2) out.Struct2 
 //goverter:matchIgnoreCase
 //goverter:ignore state
 //goverter:ignore sizeCache
 //goverter:ignore unknownFields
 Value(in.Struct3) out.Struct3
}

do this:

//goverter:matchIgnoreCase
//goverter:ignore state
//goverter:ignore sizeCache
//goverter:ignore unknownFields
type Transformer interface {
 Value(in.Struct1) out.Struct1
 Value(in.Struct2) out.Struct2
 Value(in.Struct3) out.Struct3
}

Goverter:ignore Not Respected When Converter Handles Pointers

Describe the bug
The //goverter:ignore comment on the interface method is not respected when the input and output types are pointers. This causes an error to be thrown that the field does not exist on the source when generating a converter for pointer types, even thought the field has been marked as ignored.

I've been looking at the generated code to try and trace down where the instruction is getting dropped, it looks like the internal converter function that is being generated with the case of a pointer to pointer conversion that is named {package}{T1}To{package}{T2} (e.g. govertertestInPetToGoverterTestOutPet) is what is skipping over the ignore instructions.

To Reproduce

package govertertest

type InPet struct {
	Name        string
	Description string
}

type OutPet struct {
	ID          string
	Name        string
	Description string
}

// This converter generates correctly, with the ID field being ignored

// goverter:converter
type PetConverter interface {
	// goverter:ignore ID
	Convert(in InPet) OutPet
}

// This converter causes an error to be thrown

// goverter:converter
type PetPrtConverter interface {
	// goverter:ignore ID
	Convert(in *InPet) *OutPet
}

Expected behavior
The ignore field(s) annotation should be respected for pointer conversions the same way that it is handled for regular conversions.

Add reverse operation of Source Object Mapping

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question
A clear and concise description of what the question is. Include errors and go source files.

mapping#source-object The reverse operation does not seem to be supported, what should I do?

package example

// goverter:converter
type Converter interface {
	// goverter:map Address .
	Convert(Person) FlatPerson
}

type FlatPerson struct {
	Name    string
	Age     int
	Street  string
	ZipCode string
}
type Person struct {
	Name    string
	Age     int
	Address Address
}
type Address struct {
	Street  string
	ZipCode string
}

Support `mapExtend` with more usage

Currently

  1. mapExtend can not support pass pointer type of source as parameter
  2. mapExtend may support pass particular field of source as parameter
// goverter:converter
type Converter interface {
	// goverter:mapExtend FullName ExtendFullName
	// goverter:mapExtend Age DefaultAge
	Convert(source *Input) *Output

	// goverter:mapExtend LastName FullName ExtendWithSpecName
	ConvertMeta(source *Input) *OutputMeta
}

type Input struct {
	ID        int
	FirstName string
	LastName  string
}
type Output struct {
	ID       int
	FullName string
	Age      int
}

type OutputMeta struct {
	ID       int
	FullName string
}

func ExtendFullName(source *Input) string {
	return source.FirstName + " " + source.LastName
}
func DefaultAge() int { return 42 }

func ExtendWithSpecName(name string) string {
	return name + " Spec"
}

Code generated

// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package generated

import mapextend "github.com/jmattheis/goverter/example/mapextend"

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source *mapextend.Input) *mapextend.Output {
	var pMapextendOutput *mapextend.Output
	if source != nil {
		mapextendOutput := c.mapextendInputToMapextendOutput(*source)
		pMapextendOutput = &mapextendOutput
	}
	return pMapextendOutput
}
func (c *ConverterImpl) ConvertMeta(source *mapextend.Input) *mapextend.OutputMeta {
	var pMapextendOutputMeta *mapextend.OutputMeta
	if source != nil {
		mapextendOutputMeta := c.mapextendInputToMapextendOutputMeta(*source)
		pMapextendOutputMeta = &mapextendOutputMeta
	}
	return pMapextendOutputMeta
}
func (c *ConverterImpl) mapextendInputToMapextendOutput(source mapextend.Input) mapextend.Output {
	var mapextendOutput mapextend.Output
	mapextendOutput.ID = source.ID
	mapextendOutput.FullName = mapextend.ExtendFullName(&source)
	mapextendOutput.Age = mapextend.DefaultAge()
	return mapextendOutput
}
func (c *ConverterImpl) mapextendInputToMapextendOutputMeta(source mapextend.Input) mapextend.OutputMeta {
	var mapextendOutputMeta mapextend.OutputMeta
	mapextendOutputMeta.ID = source.ID
	mapextendOutputMeta.FullName = mapextend.ExtendWithSpecName(source.LastName)
	return mapextendOutputMeta
}

Support Nested Goverter Interface Definition

i suggest goverter can support nested interface definition, so that we can assign function into different module.
it will be more clear definition.

type StructA struct {
	Value  string
	Target TargetA
}

type StructB struct {
	Value  string
	Target TargetB
}

type TargetA struct {
	TA string
}

type TargetB struct {
	TB string
}

// goverter:converter
type TargetConvertor interface {
	// goverter:map TA TB
	TargetAToTargetB(a TargetA) TargetB
}

// goverter:converter
type StructConvertor interface {
	TargetConvertor	
	StructAToStructB(a StructA) StructB
}

Converting struct with time.Time fields generate invalid converter

Describe the bug
When generating a converter between two struct with time.Time fields, the generated converter doesn't compile.

To Reproduce
Here is the code to generate the issue

//go:generate go run github.com/jmattheis/goverter/cmd/goverter github.com/jmattheis/goverter/example/simple
package simple

import "time"

// goverter:converter
type Converter interface {
	Convert(source []Input) []Output
}

type Input struct {
	Name     string
	Birthday time.Time
	Age      int
}

type Output struct {
	Name     string
	Birthday time.Time
	Age      int
}

Expected behavior
Since the source and destination types are both time.Time, I would expect a simple assignment to be done in the converter, but instead, I have a timeLocationToTimeLocation and timeTimeToTimeTime methods generated and trying to play with private fields of the time type.

slice goverter:mapIdentity bug

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
image

image

Expected behavior
A clear and concise description of what you expected to happen.

Support types.Struct for embedded structs

Is your feature request related to a problem? Please describe.
There are several structs I cannot edit and have Nested structs. For example

package converter

// goverter:converter
type Converter interface {
	ConvertHouse(source DBHouseNames) APIHouseNames
}

type DBHouseNames struct {
	House struct {
		Name string
	}
}

type APIHouseNames struct {
	House struct {
		Name string
	}
}

The resulting code panics because goverter does not support *types.Struct.

Describe the solution you'd like
Goverter to handle nested structs

Add ignore missing fields support

Currently, fields to be ignored must be explicitly specified using a // goverter:ignore FieldName comment.

I.e.:

if _, ignore := ctx.IgnoredFields[targetField.Name()]; ignore {

However, in my case I want to ignore missing fields by default.

Possible solutions I can think of:

  • A new comment, e.g. // goverter:ignoreAll
  • Allow regexp in the ignore names, e.g. // goverter:ignore To.* (in my case, I could do: // goverter:ignore .*

Hang / infinite loop on nested recursive slice structs

Describe the bug
Goverter hangs on following input

To Reproduce

// goverter:converter
type Converter interface {
	Convert(source ShopDto) ShopModel
}

type ShopDto struct {
	Name  string
	Owner *PersonDto
}

type ShopModel struct {
	Name  string
	Owner *PersonModel
}

type PersonDto struct {
	Name    string
	Friends []PersonDto
}

type PersonModel struct {
	Name    string
	Friends []PersonModel
}

// or 
type PersonDto struct {
	Name    string
	Friends *[]PersonDto
}

type PersonModel struct {
	Name    string
	Friends *[]PersonModel
}

workaround is to add typ converter signature for persondto -> personmodel to converter interface

Flatten nested fields

Have you read the project readme?

  • Yes, but it does not include related information regarding my question.
  • Yes, but the steps described do not work.
  • Yes, but I am having difficulty understanding it and want clarification.

Describe your question

If I want to map the following scenario, do I need to go custom or is there a way to do that using goverter:map ? I basically want to flatten the nested Address field at the root of APIPerson.

// goverter:converter
type Converter interface {
	ConvertPerson(source Person) APIPerson
}

type Person struct {
	Name    string
	Address Address
}

type Address struct {
	Civic  string
	Street string
	City   string
}

type APIPerson struct {
	Name   string
	Civic  string
	Street string
	City   string
}

Self import when output is in same package

When overriding the package and output the resulting generated code imports it's own package for custom implementations.

See example project: https://github.com/hshorter/goverterissue
When the generate stage runs, the resulting code contains a self import error:

// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package converter

import converter "github.com/hshorter/goverterissue/pkg/converter"

type DeviceConverterImpl struct{}

func (c *DeviceConverterImpl) ConvertToOutput(source converter.Input) converter.Output {
	var converterOutput converter.Output
	converterOutput.Name = source.Name
	converterOutput.Time = converter.timeToTime(source.Time)
	return converterOutput
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.