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.