I'm creating an API, and I have two different structs for JSON responses; one for single records and one for collection of records:
type Model struct {
Id uint
}
type Collection struct {
Records []Model
}
A Model
is just a struct representation of database data (for say, a User), and a Collection
is a collection of models.
The issue is that there will be separate structs that embed the Model
type, like so:
type User struct {
*Model
Name string
}
And since a User
doesn't satisfy the Model
type, I can't append it to the Collection
struct like this:
user := User{&Model{1}, "Jon"}
uc := &Collection{[]User{user}}
How can I make the Records struct accept struct types that implement Model
?
From the article structs in Go, we know that a struct type can have many fields. Each field is composed of one field name and one field type. In fact, sometimes, a struct field can be composed of a field type only. The way to declare struct fields is called type embedding.
Go by Example: Struct EmbeddingGo supports embedding of structs and interfaces to express a more seamless composition of types. This is not to be confused with //go:embed which is a go directive introduced in Go version 1.16+ to embed files and folders into the application binary.
Polymorphism is the ability of a message to be displayed in more than one form. Polymorphism is considered as one of the important features of Object-Oriented Programming and can be achieved during either at runtime or compile time.
Composition is a method employed to write re-usable segments of code. It is achieved when objects are made up of other smaller objects with particular behaviors, in other words, Larger objects with a wider functionality are embedded with smaller objects with specific behaviors.
You have two options:
Implementing separate collections like UserCollection
, BarCollection
, FooCollection
is an option if you do not have too many foos and bars.
The other way is to use either the generic empty interface type interface{}
which will hold values of any type (since every instance of every type satisfies the empty interface):
type Collection struct {
Records []interface{}
}
This has a number of drawbacks such as that you have to do a type assertion every time you access the Records
field, that you can put everything in there by mistake and that you have to use reflection to do most manipulations.
The better way to use interfaces would be to implement a model interface and let every type that is a model implement the interface:
type Model interface {
ImplementsModel()
}
type BaseModel struct {
Id uint
}
func (b *BaseModel) ImplementsModel() {}
type User struct {
*BaseModel
Name string
}
type Collection struct {
Records []Model
}
Of course you can remove the ImplementsModel
dummy method once you have meaningful methods. This dummy method is just there to distinguish the interface from the empty interface so that you cannot put integer values or strings in place of actual models into the collection.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With