jmattheis / goverter

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

Idiomatic way to do deep target mapping #162

Open MattiasMartens opened 4 days ago

MattiasMartens commented 4 days ago

Have you read the project documentation?

Describe your question

This question is half "how do I do this" and half "what are the nature of the limitations".

I am trying to write a converter that describes how to map one field to another. The nature of the mapping is that a field on my source object drives a field on another object that is embedded more than one layer deep.

I know the goverter mapping does not allow targets as paths but I am struggling to understand what the proper solution here is.

Per the documentation, it seems the case I'm in is "un-flattening" and the suggestion is to use map DOT TARGET; but unless I’m greatly misunderstanding something, that only works when the target fields are nested exactly one level deep and have the same names.

Source code

/** My source type */
type Ingestable struct {
  EntityLocationDesignationShortCode string
  EntityLocationDesignationLongCode string
}

/** My target type - layer 1 */
type MyEntity struct {
  EntityLocation EntityLocation
}

/** My target type - layer 2 */
type EntityLocation struct {
  LocationDesignator LocationDesignator
}

/** My target type - layer 3 */
type LocationDesignator struct {
  LocationShortCode string // Ingested field "EntityLocationDesignationShortCode" belongs here
    LocationLongCode string // Ingested field "EntityLocationDesignationLongCode" belongs here
}

// goverter:converter
type Converter interface {
  // Can't define the conversion from here...
  ConvertIngestableToMyEntity(source Ingestable) MyEntity

  // I could use map . LocationDesignator here except that the fields have different names...
  ConvertIngestableToEntityLocation(source Ingestable) EntityLocation

  // I can specify the mapping here, but how to get ConvertIngestableToMyEntity to then use it?
  // goverter:map EntityLocationDesignationShortCode LocationShortCode
  // goverter:map EntityLocationDesignationLongCode LocationLongCode
  ConvertIngestableToLocationDesignator(source Ingestable) LocationDesignator
}

I know I can solve my problem by specifying a mapping function, but:

  1. In my use-case, the goverter annotations are driving a documentation artifact, so it's desirable to capture as much of the schema definition as possible;
  2. It seems like I shouldn't have to, and I'm trying to understand what limitation I am running up against. Is this something that isn't implemented but could be, or something that should basically be considered insurmountable under the limitations of go?
jmattheis commented 3 days ago

You could currently solve it like this:

// goverter:converter
type Converter interface {
    // goverter:map . EntityLocation
    ConvertIngestableToMyEntity(source Ingestable) MyEntity

    // goverter:map . LocationDesignator
    ConvertIngestableToEntityLocation(source Ingestable) EntityLocation

    // goverter:map EntityLocationDesignationShortCode LocationShortCode
    // goverter:map EntityLocationDesignationLongCode LocationLongCode
    ConvertIngestableToLocationDesignator(source Ingestable) LocationDesignator
}

With

    // goverter:map . EntityLocation
    ConvertIngestableToMyEntity(source Ingestable) MyEntity

you basically say, may Ingestable to tho EntityLocation property of type EntityLocation. Now goverter tries to convert Ingestable -> EntityLocation, sees another user defined method ConvertIngestableToEntityLocation(source Ingestable) EntityLocation and uses that one.

It should be possible to support nested path on as TARGET in goverter:map, but there are some edge-cases that are difficult and need thinking how they should be implemented. The feature is requested in #80.