Mar 10, 2024 · 15 mins read
Using Reflection in Go
Using Go's reflect package to filter based on selective fields on structUmakant Vashishtha
Using Reflection in Go
Introduction
Go is strongly typed language, which means that all types are known at compile time.
But sometimes we need to write code that can work with information that are not known at compile time.
For example, suppose we have a struct User
as defined below:
type User struct {
Name string
Email string
DateOfBirth int
IsActive bool
}
And we want to give the option to filter the users based on multiple fields like Name
, Email
, DateOfBirth
, IsActive
etc.
For that we can define a function like this:
func FilterUsersUsingStruct(filter User) []User {
var result []User
for _, user := range users {
if user.Name == filter.Name &&
user.Email == filter.Email &&
user.DateOfBirth == filter.DateOfBirth &&
user.IsActive == filter.IsActive {
result = append(result, user)
}
}
return result
}
But there are few problems with this approach: It does not allow us to filter only on selective fields.
For example, we may want to filter only on Name
and Email
and ignore DateOfBirth
and IsActive
.
But with this approach, we have to provide all the fields in the filter struct,
or if we don’t then some default values are assigned to the fields like 0
for DateOfBirth
and false
for IsActive
as per go zero values.
So, to solve this problem, we can use Reflection
in Go.
What is Reflection
Reflection is the ability of a program to examine its own structure, particularly through the types. It’s also the ability to manipulate the values of those types.
Reflection is a powerful feature that allows us to write more flexible and dynamic code.
The reflect
package
The reflect
package in Go provides the ability to inspect the type and value of a variable at runtime.
Using reflect, we can inspect the fields of a struct, the methods of a type, and the value of a variable.
This sounds promising for what we want to achieve. We can use reflect
to filter based on selective fields.
Filter Argument with Selective Fields
We can define the filter argument to be a map of string to interface{} (map[string]interface{}
)
where the key is the field name and the value is the value to filter on.
var filterArgument = map[string]interface{}{
"Name": "John Doe",
"Email": "john.doe@example.com",
}
Using reflect
with Structs
reflect.ValueOf
reflect.ValueOf
takes any variable and returns reflect.Value
which has some useful reflection related function on the passed object.
reflectUser := reflect.ValueOf(user)
reflect.Value.FieldByName
reflect.Value.FieldByName
returns the struct field with the given name.
nameField := reflectUser.FieldByName("Name")
reflect.Value.Interface
reflect.Value.Interface
returns the value of the reflect.Value
as an interface{}
.
nameValue := nameField.Interface()
We the above functions we can write a function to iterate over the key
, values
of the filter map
and compare each field with the value of the struct.
Final Code
package users
import (
"reflect"
)
type User struct {
Name string
Email string
DateOfBirth int
IsActive bool
}
var users = []User{
{
Name: "John Doe",
Email: "john.doe@example.com",
DateOfBirth: 1234567890,
IsActive: true,
},
{
Name: "Jane Doe",
Email: "jane.doe@example.com",
DateOfBirth: 1234567890,
IsActive: false,
},
}
func filterUserReflect(user User, filter map[string]interface{}) bool {
reflectUser := reflect.ValueOf(user)
for key, value := range filter {
field := reflectUser.FieldByName(key)
if !field.IsValid() {
return false
}
entityValue := field.Interface()
if entityValue != value {
return false
}
}
return true
}
func FilterUsers(filter map[string]interface{}) []User {
var result []User
for _, user := range users {
if filterUserReflect(user, filter) {
result = append(result, user)
}
}
return result
}
More on reflect
package
The reflect
package provides many more functions to inspect the type and value of a variable at runtime.
A lot of other libraries and frameworks use reflect
package to provide dynamic features.
Some of the popular libraries that use reflect
package are:
Caution
Using reflect
package can be slow and error-prone. Use it only when necessary and don’t overuse it.
Using reflect
package can make the code harder to read and understand.