SoFunction
Updated on 2025-03-05

How to use reflection in the Go series tutorial

Reflection is one of the advanced topics in Go. I will make it as simple as possible.

This tutorial is divided into the following sections.

  • What is reflection?
  • Why do we need to check variables and determine the type of variables?
  • reflect package
    • and
    • NumField() and Field() methods
    • Int() and String() methods
  • Complete program
  • Should we use reflection?

Let's discuss these chapters one by one.

What is reflection?

Reflection is the program being able to check variables and values ​​at runtime to find their types. You may not understand it very much, it doesn't matter. After this tutorial is over, you will understand reflection clearly, so follow our tutorial to learn.

Why do we need to check variables and determine the type of variables?

When learning reflection, the first doubt that everyone faces is: if each variable in the program is defined by ourselves, then you can know the variable type at compile time. Why do we still need to check the variable at runtime and find its type? Yes, it's the case most of the time, but not always.

Let me explain it. Let's write a simple program below.

package main

import (
 "fmt"
)

func main() {
 i := 10
 ("%d %T", i, i)
}

Run on playground

In the above program, the type of i is known at compile time, and then we print out i on the next line. There is nothing special here.

Now let’s understand the situation where variable types need to be found at runtime. Suppose we want to write a simple function that takes the structure as a parameter and uses it to create a SQL insert query.

Consider the following procedure:

package main

import (
 "fmt"
)

type order struct {
 ordId  int
 customerId int
}

func main() {
 o := order{
  ordId:  1234,
  customerId: 567,
 }
 (o)
}

Run on playground

In the above program, we need to write a function that receives the structure variable o as a parameter and returns the following SQL insert query.

insert into order values(1234, 567)

This function is very simple to write. We now write this function.

package main

import (
 "fmt"
)

type order struct {
 ordId  int
 customerId int
}

func createQuery(o order) string {
 i := ("insert into order values(%d, %d)", , )
 return i
}

func main() {
 o := order{
  ordId:  1234,
  customerId: 567,
 }
 (createQuery(o))
}

Run on playground

In line 12, the createQuery function creates an insert query using two fields of o (ordId and customerId). The program will output:

insert into order values(1234, 567)

Now let's upgrade this query generator. What if we want it to be universal and can be applied to any structure type? Let's use the program to understand it.

package main

type order struct {
 ordId  int
 customerId int
}

type employee struct {
 name string
 id int
 address string
 salary int
 country string
}

func createQuery(q interface{}) string {
}

func main() {

}

Our goal is to complete the createQuery function (line 16 in the above program), which can take any structure as a parameter and create an insert query based on the fields of the structure.

For example, if we pass in the following structure:

o := order {
 ordId: 1234,
 customerId: 567
}

The createQuery function should return:

insert into order values (1234, 567)

Similarly, if we pass in:

 e := employee {
  name: "Naveen",
  id: 565,
  address: "Science Park Road, Singapore",
  salary: 90000,
  country: "Singapore",
 }

This function returns:

insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")

Since the createQuery function should work with any structure, it receives interface{} as an argument. For simplicity, we only deal with structs containing fields of type string and int, but can be extended to include fields of any type.

The createQuery function should work with all structures. Therefore, to write this function, you must check the type of the passed structure parameter at runtime, find the structure field, and then create a query. Reflection is needed at this time. In the next step in this tutorial, we will learn how to implement it using the reflect package.

reflect package

In Go, reflect implements runtime reflection. The reflect package helps identify the underlying specific types and specific values ​​of the interface{} variable. This is exactly what we need. The createQuery function receives the interface{} parameter and creates SQL query based on its specific type and value. This is exactly where the reflect package can help us.

Before writing our common query generator, we first need to understand several types and methods in the reflect package. Let's learn about it one by one.

and

Represents the specific type of interface{}, and represents its specific value. The two functions () and () can return and respectively. These two types are the basis for us to create a query generator. We now use a simple example to understand these two types.

package main

import (
 "fmt"
 "reflect"
)

type order struct {
 ordId  int
 customerId int
}

func createQuery(q interface{}) {
 t := (q)
 v := (q)
 ("Type ", t)
 ("Value ", v)


}
func main() {
 o := order{
  ordId:  456,
  customerId: 56,
 }
 createQuery(o)

}

Run on playground

In the above program, the createQuery function in line 13 receives interface{} as a parameter. On line 14, the parameter interface{} is received, and it returns, which contains the specific type of the incoming interface{} parameter. Similarly, on line 15, the function receives the parameter interface{} and returns , which contains the specific value of the coming interface{}.

The above program will print:

Type 
Value  {456 56}

From the output we can see that the program prints the specific type and specific value of the interface.


There is another important type in the reflect package: Kind.

In the reflective package, the types of Kind and Type may look similar, but in the following program, it is clear how they differ.

package main

import (
 "fmt"
 "reflect"
)

type order struct {
 ordId  int
 customerId int
}

func createQuery(q interface{}) {
 t := (q)
 k := ()
 ("Type ", t)
 ("Kind ", k)


}
func main() {
 o := order{
  ordId:  456,
  customerId: 56,
 }
 createQuery(o)

}

Run on playground

The above program will output:

Type 
Kind  struct

I think you should be very clear about the difference between the two. Type represents the actual type of interface{} (here is ) and Kind represents the specific category of that type (here is struct).

NumField() and Field() methods

The NumField() method returns the number of fields in the structure, while the Field(i int) method returns the ' field i'.

package main

import (
 "fmt"
 "reflect"
)

type order struct {
 ordId  int
 customerId int
}

func createQuery(q interface{}) {
 if (q).Kind() ==  {
  v := (q)
  ("Number of fields", ())
  for i := 0; i < (); i++ {
   ("Field:%d type:%T value:%v\n", i, (i), (i))
  }
 }

}
func main() {
 o := order{
  ordId:  456,
  customerId: 56,
 }
 createQuery(o)
}

Run on playground

In the above program, since the NumField method can only be used on the struct, we first checked on line 14 that the category of q is struct. The other code of the program is easy to understand and will not be explained. The program will output:

Number of fields 2
Field:0 type: value:456
Field:1 type: value:56

Int() and String() methods

Int and String can help us take out as int64 and string respectively.

package main

import (
 "fmt"
 "reflect"
)

func main() {
 a := 56
 x := (a).Int()
 ("type:%T value:%v\n", x, x)
 b := "Naveen"
 y := (b).String()
 ("type:%T value:%v\n", y, y)

}

Run on playground

In line 10 of the above program, we take out and convert it to int64, while in line 13, we take out and convert it to string. The program will output:

type:int64 value:56
type:string value:Naveen

Complete program

Now that we have enough knowledge to complete our query generator, we will implement it.

package main

import (
 "fmt"
 "reflect"
)

type order struct {
 ordId  int
 customerId int
}

type employee struct {
 name string
 id  int
 address string
 salary int
 country string
}

func createQuery(q interface{}) {
 if (q).Kind() ==  {
  t := (q).Name()
  query := ("insert into %s values(", t)
  v := (q)
  for i := 0; i < (); i++ {
   switch (i).Kind() {
   case :
    if i == 0 {
     query = ("%s%d", query, (i).Int())
    } else {
     query = ("%s, %d", query, (i).Int())
    }
   case :
    if i == 0 {
     query = ("%s\"%s\"", query, (i).String())
    } else {
     query = ("%s, \"%s\"", query, (i).String())
    }
   default:
    ("Unsupported type")
    return
   }
  }
  query = ("%s)", query)
  (query)
  return

 }
 ("unsupported type")
}

func main() {
 o := order{
  ordId:  456,
  customerId: 56,
 }
 createQuery(o)

 e := employee{
  name: "Naveen",
  id:  565,
  address: "Coimbatore",
  salary: 90000,
  country: "India",
 }
 createQuery(e)
 i := 90
 createQuery(i)

}

Run on playground

In line 22, we first check whether the passed parameter is a structure. In line 23, we use the Name() method to obtain the name of the structure from the structure's . In the next line, we use t to create the query.

On line 28, the case statement checks whether the current field is . If so, we will take the value of the field and convert it to int64 using the Int() method. The if else statement is used to handle boundary cases. Please add a log to understand why it is needed. In line 34, we use the same logic to get the string.

We also made additional checks to prevent the program from crashing when the createQuery function passes into an unsupported type. The other code of the program is self-explanatory. I suggest you add logs where appropriate and check the output to better understand this program.

The program will output:

insert into order values(456, 56)
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
unsupported type

As for adding field names to the output query, we leave it to the reader as an exercise. Please try to modify the program and print out the query in the following format.

insert into order(ordId, customerId) values(456, 56)

Should we use reflection?

We have shown the practical application of reflection and now consider a very realistic problem. Should we use reflection? I want to quote Rob Pike's motto about using reflection to answer this question.

Clarity is better than cleverness. And the reflection is not clear at a glance.

Reflection is a very powerful and advanced concept in the Go language and we should use it with caution. It is very difficult to write clear and maintainable code using reflection. You should avoid using it whenever possible and use reflection only when you have to use it.

This tutorial ends here. Hope you like it. I wish you a happy one. I hope it will be helpful to everyone's learning and I hope everyone will support me more.