How to handle unexported fields
Goverter will fail when it sees unexported fields and requires the user to define what to do. Here are ways to fix the unexported fields error in Goverter:
Ignore the unexported field
If the unexported fields can be ignored, then you can use ignore
to ignore these fields.
An example for are protobuf generated structs, for which the fields state
, sizeCache
and unknownFields
are only internally used and must not be set when constructing a new instance. It's possible to globally ignore unexported fields via ignoreUnexported
though it's discouraged to use it.
Example (click me)
package protobuf
import "goverter/example/pb"
// goverter:converter
type Converter interface {
FromProtobuf(*pb.Event) *OutputEvent
// goverter:ignore state sizeCache unknownFields
ToProtobuf(*OutputEvent) *pb.Event
}
type OutputEvent struct {
Content string
Priority int32
}
syntax = "proto3";
package event;
option go_package = "/pb";
message Event {
string content = 1;
int32 priority = 2;
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v4.24.2
// source: event.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Event struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
Priority int32 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty"`
}
func (x *Event) Reset() {
*x = Event{}
if protoimpl.UnsafeEnabled {
mi := &file_event_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Event) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Event) ProtoMessage() {}
func (x *Event) ProtoReflect() protoreflect.Message {
mi := &file_event_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Event.ProtoReflect.Descriptor instead.
func (*Event) Descriptor() ([]byte, []int) {
return file_event_proto_rawDescGZIP(), []int{0}
}
func (x *Event) GetContent() string {
if x != nil {
return x.Content
}
return ""
}
func (x *Event) GetPriority() int32 {
if x != nil {
return x.Priority
}
return 0
}
var File_event_proto protoreflect.FileDescriptor
var file_event_proto_rawDesc = []byte{
0x0a, 0x0b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x65,
0x76, 0x65, 0x6e, 0x74, 0x22, 0x3d, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a,
0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72,
0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72,
0x69, 0x74, 0x79, 0x42, 0x05, 0x5a, 0x03, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_event_proto_rawDescOnce sync.Once
file_event_proto_rawDescData = file_event_proto_rawDesc
)
func file_event_proto_rawDescGZIP() []byte {
file_event_proto_rawDescOnce.Do(func() {
file_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_event_proto_rawDescData)
})
return file_event_proto_rawDescData
}
var file_event_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_event_proto_goTypes = []interface{}{
(*Event)(nil), // 0: event.Event
}
var file_event_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_event_proto_init() }
func file_event_proto_init() {
if File_event_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_event_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Event); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_event_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_event_proto_goTypes,
DependencyIndexes: file_event_proto_depIdxs,
MessageInfos: file_event_proto_msgTypes,
}.Build()
File_event_proto = out.File
file_event_proto_rawDesc = nil
file_event_proto_goTypes = nil
file_event_proto_depIdxs = nil
}
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
//go:build !goverter
package generated
import (
example "goverter/example"
pb "goverter/example/pb"
)
type ConverterImpl struct{}
func (c *ConverterImpl) FromProtobuf(source *pb.Event) *example.OutputEvent {
var pProtobufOutputEvent *example.OutputEvent
if source != nil {
var protobufOutputEvent example.OutputEvent
protobufOutputEvent.Content = (*source).Content
protobufOutputEvent.Priority = (*source).Priority
pProtobufOutputEvent = &protobufOutputEvent
}
return pProtobufOutputEvent
}
func (c *ConverterImpl) ToProtobuf(source *example.OutputEvent) *pb.Event {
var pPbEvent *pb.Event
if source != nil {
var pbEvent pb.Event
pbEvent.Content = (*source).Content
pbEvent.Priority = (*source).Priority
pPbEvent = &pbEvent
}
return pPbEvent
}
Shallow copy
If the unexported field is inside an immutable struct, you can skip the deep copying by defining an extend
or map [SOURCE_PATH] TARGET | METHOD
method that returns the input.
An example for this use-case would be the time.Time
struct from the Go standard library. The type is immutable and therefore can be safely passed through.
Example (click me)
package simple
import "time"
// goverter:converter
// goverter:extend ConvertTime
type Converter interface {
Convert(source Input) Output
}
// It is possible to only call the ConvertTime function
// for one specific conversion method like this
// goverter:converter
type ConverterLocally interface {
// goverter:map CreatedAt | ConvertTime
Convert(source Input) Output
}
func ConvertTime(t time.Time) time.Time {
return t
}
type Input struct {
Name string
CreatedAt time.Time
}
type Output struct {
Name string
CreatedAt time.Time
}
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
//go:build !goverter
package generated
import time "github.com/jmattheis/goverter/example/time"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source time.Input) time.Output {
var simpleOutput time.Output
simpleOutput.Name = source.Name
simpleOutput.CreatedAt = time.ConvertTime(source.CreatedAt)
return simpleOutput
}
type ConverterLocallyImpl struct{}
func (c *ConverterLocallyImpl) Convert(source time.Input) time.Output {
var simpleOutput time.Output
simpleOutput.Name = source.Name
simpleOutput.CreatedAt = time.ConvertTime(source.CreatedAt)
return simpleOutput
}
Use a constructor
If the struct containing the unexported fields can be instantiated via a constructor you can use extend
or map [SOURCE_PATH] TARGET | METHOD
Example (click me)
package example
import (
"bytes"
"io"
)
// goverter:converter
// goverter:extend ReaderForBytes
type ConverterGlobalMethod interface {
// goverter:map Content Reader
Convert(source *Input) *Output
}
func ReaderForBytes(content []byte) io.Reader {
return bytes.NewReader(content)
}
// You can also specify this method for a specific property conversion
// goverter:converter
type ConverterPropertyMethod interface {
// goverter:map Content Reader | bytes:NewReader
Convert(source *Input) *Output
}
type Input struct {
Source string
Content []byte
}
type Output struct {
Source string
Reader io.Reader
}
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
//go:build !goverter
package generated
import (
"bytes"
constructor "github.com/jmattheis/goverter/example/constructor"
)
type ConverterGlobalMethodImpl struct{}
func (c *ConverterGlobalMethodImpl) Convert(source *constructor.Input) *constructor.Output {
var pExampleOutput *constructor.Output
if source != nil {
var exampleOutput constructor.Output
exampleOutput.Source = (*source).Source
exampleOutput.Reader = constructor.ReaderForBytes((*source).Content)
pExampleOutput = &exampleOutput
}
return pExampleOutput
}
type ConverterPropertyMethodImpl struct{}
func (c *ConverterPropertyMethodImpl) Convert(source *constructor.Input) *constructor.Output {
var pExampleOutput *constructor.Output
if source != nil {
var exampleOutput constructor.Output
exampleOutput.Source = (*source).Source
exampleOutput.Reader = bytes.NewReader((*source).Content)
pExampleOutput = &exampleOutput
}
return pExampleOutput
}