Contents
Reflection in Go
Reflection
A language that supports first-class functions allows functions to be:
- Assigned to variables.
- Passed as arguments to other functions.
- Returned from other functions.
Why Inspect a Variable’s Type at Runtime?
At first glance, it might seem unnecessary to inspect a variable’s type at runtime since variable types are usually defined at compile time. However, this is not always the case, especially when working with generic code or handling data of unknown types.
Example:
package main
import "fmt"
func main() {
i := 42
fmt.Printf("Value: %d, Type: %T\n", i, i)
}
Output:
Value: 42, Type: int
Generic Query Generator Example
package main
type product struct {
productId int
productName string
}
type customer struct {
customerName string
customerId int
age int
}
We want a function createQuery
that works for any struct. For example:
If given:
p := product{
productId: 101,
productName: "Laptop",
}
It should generate:
c := customer{
customerName: "Alice",
customerId: 1,
age: 30,
}
If given:
c := customer{
customerName: "Alice",
customerId: 1,
age: 30,
}
It should generate:
insert into customer(customerName, customerId, age) values("Alice", 1, 30)
Using Reflection
The reflect
package in Go provides tools to achieve this. Key components are:
reflect.TypeOf
: Returns the type of a value.reflect.ValueOf
: Returns the value of an interface.reflect.Kind
: Indicates the specific kind (e.g.,struct
,int
,string
) of a type.
Implementation of createQuery
package main
import (
"fmt"
"reflect"
)
func createQuery(q interface{}) {
// Ensure the input is a struct
if reflect.ValueOf(q).Kind() == reflect.Struct {
t := reflect.TypeOf(q)
v := reflect.ValueOf(q)
// Start building the query
query := fmt.Sprintf("insert into %s(", t.Name())
// Add field names
for i := 0; i < t.NumField(); i++ {
if i > 0 {
query += ", "
}
query += t.Field(i).Name
}
query += ") values("
// Add field values
for i := 0; i < v.NumField(); i++ {
if i > 0 {
query += ", "
}
switch v.Field(i).Kind() {
case reflect.Int:
query += fmt.Sprintf("%d", v.Field(i).Int())
case reflect.String:
query += fmt.Sprintf("\"%s\"", v.Field(i).String())
default:
fmt.Println("Unsupported field type")
return
}
}
query += ")"
fmt.Println(query)
} else {
fmt.Println("Unsupported type")
}
}
Example Usage
func main() {
p := product{
productId: 101,
productName: "Laptop",
}
createQuery(p)
c := customer{
customerName: "Alice",
customerId: 1,
age: 30,
}
createQuery(c)
x := 42
createQuery(x)
}
Output:
insert into product(productId, productName) values(101, "Laptop")
insert into customer(customerName, customerId, age) values("Alice", 1, 30)
Unsupported type
This implementation demonstrates how reflection allows us to work with types dynamically at runtime, enabling powerful generic programming capabilities.