Saturday, August 17, 2019

Create an interface array consisting of a generic struct type in golang

I was looking at how to get a result set from a golang sql query. It seemed that a lot of the examples explicitly defined the fields or declared a struct that are passed into the Scan() method such as:

var id int
var name string
err = rows.Scan(&id, &name)
// or
row = MyStruct{}

err = rows.Scan(&row.Id, &row.Name)

This seemed like a lot of repetition if every query required this logic. My next thought was to see if I could pass in a struct, use reflect to obtain the fields and return a slice of values.

Here are two examples, the first one uses uses append and requires type assertion to access the fields. The second example uses reflect.append and each field can be accessed directly from the element.

Note: Additional checks should be added, such as making sure the address of the object is passed in.

First
package main

import (
    "fmt"
    "reflect"
)

type MyStruct struct {
    Id int
    Name string
}

func main() {
    me := MyStruct{}
    result := doIt(&me)

    for _, v := range result {
        fmt.Printf("%d %s\n", v.(MyStruct).Id, v.(MyStruct).Name)
    }
}

func doIt(genericStruct interface{}) []interface{} {
    params := []interface{}{}
    var myStruct reflect.Value

    structType := reflect.TypeOf(genericStruct).Elem()

    // First Element
    myStruct = reflect.New(structType).Elem()
    myStruct.FieldByName("Id").SetInt(1)
    myStruct.FieldByName("Name").SetString("one")
    fmt.Printf("%#v\n", myStruct)

    params = append(params, myStruct.Interface())

    // Second Element
    myStruct = reflect.New(structType).Elem()
    myStruct.FieldByName("Id").SetInt(2)
    myStruct.FieldByName("Name").SetString("two")
    fmt.Printf("%#v\n", myStruct)

    params = append(params, myStruct.Interface())

    return params
}

Second
package main

import (
    "fmt"
    "reflect"
)

type MyStruct struct {
    Id int
    Name string
}

func main() {
    myStruct := []MyStruct{}
    doIt(&myStruct)

    for _, v := range myStruct {
        fmt.Printf("%d %s\n", v.Id, v.Name)
    }
}

func doIt(genericStructArray interface{}) {
    var myStruct reflect.Value

    genericValue := reflect.ValueOf(genericStructArray).Elem()
    genericType := genericValue.Type().Elem()

    // First Element
    myStruct = reflect.New(genericType).Elem()
    myStruct.FieldByName("Id").SetInt(1)
    myStruct.FieldByName("Name").SetString("one")
    fmt.Printf("%#v\n", myStruct)

    genericValue.Set(reflect.Append(genericValue, myStruct))

    // Second Element
    myStruct = reflect.New(genericType).Elem()
    myStruct.FieldByName("Id").SetInt(2)
    myStruct.FieldByName("Name").SetString("two")
    fmt.Printf("%#v\n", myStruct)

    genericValue.Set(reflect.Append(genericValue, myStruct))
}

Output (same for both)
main.MyStruct{Id:5, Name:"one"}
main.MyStruct{Id:2, Name:"two"}
5 one
2 two