리플렉션 (reflect 패키지) 를 사용하면 구조체를 배열처럼 인덱스로 접근할 수 있습니다.

import(
    "fmt"
    "reflect"
    "strconv"
)

type test struct{
    A int
    B int
    C string
}

값 읽기

t := test{1,2,"3"}

fmt.Println(reflect.ValueOf(t).Field(0).Int())
fmt.Println(reflect.ValueOf(t).Field(1).Int())
fmt.Println(reflect.ValueOf(t).Field(2).String())

결과

1
2
3

자료형이 일치하지 않을 경우 패닉이 발생합니다.

t := test{1,2,"3"}

fmt.Println(reflect.ValueOf(t).Field(0).Int())
fmt.Println(reflect.ValueOf(t).Field(1).Int())
fmt.Println(reflect.ValueOf(t).Field(2).Int())

결과

1
2
panic: reflect: call of reflect.Value.Int on string Value

그러므로 값을 읽기 전에는 Kind() 함수를 이용해 타입 검사를 해 주어야 합니다.

t := test{1,2,"3"}

for i:=0;i<3;i++{
    if reflect.ValueOf(t).Field(i).Kind()==reflect.String{
        fmt.Println(reflect.ValueOf(t).Field(i).String())
    }else{
        fmt.Println(reflect.ValueOf(t).Field(i).Int())
    }
}

결과

1
2
3

값 쓰기

var t test

reflect.ValueOf(&t).Elem().Field(0).SetInt(int64(1))
reflect.ValueOf(&t).Elem().Field(1).SetInt(int64(2))
reflect.ValueOf(&t).Elem().Field(2).SetString("3")

fmt.Println(t)

결과

{1 2 3}

값을 읽는 코드와 비슷합니다. 다만 구조체 필드의 값을 변경하기 위해 ValueOf 함수의 파라미터로 포인터를 넘겼고 역참조를 위해 Elem() 함수를 사용하고 있습니다.

값 쓰기의 경우에도 읽기와 마찬가지로 자료형이 일치하지 않을 경우 패닉이 발생합니다.

var t test

reflect.ValueOf(&t).Elem().Field(0).SetInt(int64(1))
reflect.ValueOf(&t).Elem().Field(1).SetInt(int64(2))
reflect.ValueOf(&t).Elem().Field(2).SetInt(int64(3))

fmt.Println(t)

결과

panic: reflect: call of reflect.Value.SetInt on string Value

그러므로 Kind() 함수를 이용해 타입 검사를 해 주어야 합니다.

var t test

a:=[]int{1,2,3}

for i,n:=range a{
    if reflect.ValueOf(t).Field(i).Kind()==reflect.String{
        reflect.ValueOf(&t).Elem().Field(i).SetString(strconv.Itoa(n))
    }else{
        reflect.ValueOf(&t).Elem().Field(i).SetInt(int64(n))
    }
}

fmt.Println(t)

결과

{1 2 3}

쓰기의 경우 읽기와는 다르게 구조체 필드가 내보내기(Export)되지 않았을 경우(=앞 글자가 대문자가 아닐 경우) 패닉이 발생하므로 주의해야 합니다.