Skip to content

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)
go
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
}
proto
syntax = "proto3";
package event;
option go_package  = "/pb";

message Event {
  string content = 1;
  int32 priority = 2;
}
go
// 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
}
go
// 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)
go
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
}
go
// 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)
go
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
}
go
// 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
}