jmattheis / goverter

Generate type-safe Go converters by simply defining an interface
https://goverter.jmattheis.de/
MIT License
487 stars 46 forks source link

Combine nested automap and fieldmap #80

Open goghcrow opened 1 year ago

goghcrow commented 1 year ago

When I Combine automap and fieldmap, it doesn't work

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

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

type Person struct {
    Name string
    Age  int
    Address
}
type Address struct {
    StreetName string
    ZipCode    string
}

the error message was as follows

| github.com/jmattheis/goverter/example/nested.FlatPerson
|
|      | goverter:map . Address
|      |
|      |
|      |
source.       .???
target.Address.StreetName
|      |       |
|      |       | string
|      |
|      | github.com/jmattheis/goverter/example/nested.Address
|
| github.com/jmattheis/goverter/example/nested.Person

Cannot match the target field with the source entry: "StreetName" does not exist.

The solution might be to add the sub context when createing subMethod PR

It works fine.

package generated

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

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source nested.FlatPerson) nested.Person {
    var nestedPerson nested.Person
    nestedPerson.Name = source.Name
    nestedPerson.Age = source.Age
    nestedPerson.Address = c.nestedFlatPersonToNestedAddress(source)
    return nestedPerson
}
func (c *ConverterImpl) nestedFlatPersonToNestedAddress(source nested.FlatPerson) nested.Address {
    var nestedAddress nested.Address
    nestedAddress.StreetName = source.Street
    nestedAddress.ZipCode = source.ZipCode
    return nestedAddress
}

It works fine, too, and even handles nested cases. But I don't know if there are any other problems

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

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

type Person struct {
    Name string
    Age  int
    Address
}
type Address struct {
    StreetInfo
    ZipCode string
}
type StreetInfo struct {
    Name string
}

generated

package generated

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

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source nested.FlatPerson) nested.Person {
    var nestedPerson nested.Person
    nestedPerson.Name = source.Name
    nestedPerson.Age = source.Age
    nestedPerson.Address = c.nestedFlatPersonToNestedAddress(source)
    return nestedPerson
}
func (c *ConverterImpl) nestedFlatPersonToNestedAddress(source nested.FlatPerson) nested.Address {
    var nestedAddress nested.Address
    nestedAddress.StreetInfo = c.nestedFlatPersonToNestedStreetInfo(source)
    nestedAddress.ZipCode = source.ZipCode
    return nestedAddress
}
func (c *ConverterImpl) nestedFlatPersonToNestedStreetInfo(source nested.FlatPerson) nested.StreetInfo {
    var nestedStreetInfo nested.StreetInfo
    nestedStreetInfo.Name = source.Street
    return nestedStreetInfo
}

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

jmattheis commented 1 year ago

You can already achieve the same generated code by doing the following:

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

    // goverter:map Street StreetName
    ConvertAddress(FlatPerson) Address
}

Sadly your solution isn't that easy, because it can clash with other definitions, imagine a converter like this

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

    // goverter:map Name StreetName
    ConvertAddress(FlatPerson) Address
}

What should be the value for StreetName (Name or Street). I'm not fully against a change like this, but goverter must fail with a readable error when there are conflicting definitions.

goghcrow commented 1 year ago

It still don't work when FlatPerson is ptr. It's a simplified version of my actual scenario.

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

    // goverter:map Street StreetName
    ConvertAddress(*FlatPerson) Address
}
| *github.com/jmattheis/goverter/example/nested.FlatPerson
|
|     | github.com/jmattheis/goverter/example/nested.FlatPerson
|     |
|     | | goverter:map . Address
|     | |
|     | |
|     | |
source*.       .???
target*.Address.StreetName
|     | |       |
|     | |       | string
|     | |
|     | | github.com/jmattheis/goverter/example/nested.Address
|     |
|     | github.com/jmattheis/goverter/example/nested.Person
|
| *github.com/jmattheis/goverter/example/nested.Person
jmattheis commented 1 year ago

Try defining it as

// goverter:map Street StreetName
ConvertAddress(FlatPerson) Address

See the mapping in the error message. It first does source* which is a non pointer and then the goverter:map . Address is used.