[Go] 자료형 출력
Updated:
개요
Go의 reflect 패키지는 런타임에 타입과 값 정보를 검사하고 조작할 수 있는 리플렉션 기능을 제공합니다.
주요 특징:
- 타입 검사: TypeOf로 런타임 타입 확인
- 값 검사: ValueOf로 값 정보 조회
- Kind 분류: 기본 타입 카테고리 구분
- 구조체 분석: 필드, 메소드, 태그 검사
- 동적 호출: 함수/메소드 런타임 실행
- 타입 변환: 인터페이스 간 동적 변환
- 성능 비용: 리플렉션은 느리므로 신중히 사용
기본 타입 검사
1. TypeOf - 타입 확인
import (
"fmt"
"reflect"
)
func main() {
// 기본 타입
fmt.Println(reflect.TypeOf(1)) // int
fmt.Println(reflect.TypeOf(1.2)) // float64
fmt.Println(reflect.TypeOf("hello")) // string
fmt.Println(reflect.TypeOf('a')) // int32 (rune)
fmt.Println(reflect.TypeOf(true)) // bool
// 복합 타입
fmt.Println(reflect.TypeOf([]int{1, 2})) // []int
fmt.Println(reflect.TypeOf(map[string]int{})) // map[string]int
fmt.Println(reflect.TypeOf(struct{ X int }{})) // struct { X int }
}
2. Kind - 타입 카테고리
func main() {
type MyInt int
var x MyInt = 10
t := reflect.TypeOf(x)
fmt.Println(t) // main.MyInt (타입 이름)
fmt.Println(t.Kind()) // int (기본 종류)
// Kind는 기본 분류
fmt.Println(t.Kind() == reflect.Int) // true
}
3. Type 메소드
func main() {
type Person struct {
Name string
Age int
}
t := reflect.TypeOf(Person{})
// 타입 정보
fmt.Println(t.Name()) // Person
fmt.Println(t.PkgPath()) // main
fmt.Println(t.String()) // main.Person
fmt.Println(t.Kind()) // struct
fmt.Println(t.Size()) // 24 (바이트)
fmt.Println(t.NumField()) // 2 (필드 개수)
}
4. 포인터와 Elem
func main() {
var x int = 42
var p *int = &x
// 포인터 타입
t := reflect.TypeOf(p)
fmt.Println(t) // *int
fmt.Println(t.Kind()) // ptr
// 포인터가 가리키는 타입
elem := t.Elem()
fmt.Println(elem) // int
fmt.Println(elem.Kind()) // int
}
값 검사
1. ValueOf - 값 정보
func main() {
x := 42
v := reflect.ValueOf(x)
fmt.Println(v) // 42
fmt.Println(v.Type()) // int
fmt.Println(v.Kind()) // int
fmt.Println(v.Int()) // 42
// 타입별 변환
s := "hello"
v2 := reflect.ValueOf(s)
fmt.Println(v2.String()) // hello
}
2. Interface - 원본 값 추출
func main() {
x := 42
v := reflect.ValueOf(x)
// 인터페이스로 변환
i := v.Interface()
// 타입 단언
num := i.(int)
fmt.Println(num) // 42
}
3. 수정 가능 여부
func main() {
x := 42
// 값으로 전달 (수정 불가)
v := reflect.ValueOf(x)
fmt.Println(v.CanSet()) // false
// 포인터로 전달 (수정 가능)
v2 := reflect.ValueOf(&x).Elem()
fmt.Println(v2.CanSet()) // true
v2.SetInt(100)
fmt.Println(x) // 100
}
4. 값 설정
func main() {
var x interface{} = 42
v := reflect.ValueOf(&x).Elem()
// 타입 확인 후 설정
if v.CanSet() {
switch v.Elem().Kind() {
case reflect.Int:
v.Set(reflect.ValueOf(100))
case reflect.String:
v.Set(reflect.ValueOf("hello"))
}
}
fmt.Println(x) // 100
}
구조체 검사
1. 필드 순회
type Person struct {
Name string
Age int
City string
}
func main() {
p := Person{"Alice", 30, "Seoul"}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v (type: %v)\n",
field.Name,
value.Interface(),
field.Type)
}
// Name: Alice (type: string)
// Age: 30 (type: int)
// City: Seoul (type: string)
}
2. 필드 정보
type Person struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=0,max=120"`
}
func main() {
t := reflect.TypeOf(Person{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Name: %s\n", field.Name)
fmt.Printf("Type: %v\n", field.Type)
fmt.Printf("Tag: %s\n", field.Tag)
fmt.Printf("JSON tag: %s\n", field.Tag.Get("json"))
fmt.Println()
}
}
3. 필드 값 수정
type Person struct {
Name string
Age int
}
func main() {
p := Person{"Alice", 30}
v := reflect.ValueOf(&p).Elem()
// 이름으로 필드 찾기
nameField := v.FieldByName("Name")
if nameField.IsValid() && nameField.CanSet() {
nameField.SetString("Bob")
}
// 인덱스로 필드 찾기
ageField := v.Field(1)
if ageField.CanSet() {
ageField.SetInt(25)
}
fmt.Println(p) // {Bob 25}
}
4. 중첩 구조체
type Address struct {
City string
Country string
}
type Person struct {
Name string
Address Address
}
func main() {
p := Person{
Name: "Alice",
Address: Address{
City: "Seoul",
Country: "Korea",
},
}
v := reflect.ValueOf(p)
// 중첩 필드 접근
addr := v.FieldByName("Address")
city := addr.FieldByName("City")
fmt.Println(city.String()) // Seoul
}
메소드 검사
1. 메소드 목록
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
func main() {
calc := Calculator{}
t := reflect.TypeOf(calc)
fmt.Println("Methods:")
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("- %s: %v\n", method.Name, method.Type)
}
// Methods:
// - Add: func(main.Calculator, int, int) int
// - Multiply: func(main.Calculator, int, int) int
}
2. 메소드 호출
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
func main() {
calc := Calculator{}
v := reflect.ValueOf(calc)
// 메소드 찾기
method := v.MethodByName("Add")
// 인자 준비
args := []reflect.Value{
reflect.ValueOf(10),
reflect.ValueOf(20),
}
// 호출
result := method.Call(args)
fmt.Println(result[0].Int()) // 30
}
3. 포인터 리시버
type Counter struct {
count int
}
func (c *Counter) Increment() {
c.count++
}
func (c *Counter) Get() int {
return c.count
}
func main() {
counter := &Counter{}
v := reflect.ValueOf(counter)
// Increment 호출
v.MethodByName("Increment").Call(nil)
v.MethodByName("Increment").Call(nil)
// Get 호출
result := v.MethodByName("Get").Call(nil)
fmt.Println(result[0].Int()) // 2
}
실전 예제
1. JSON 직렬화 (간단 버전)
import (
"fmt"
"reflect"
"strings"
)
func SimpleJSON(v interface{}) string {
val := reflect.ValueOf(v)
typ := reflect.TypeOf(v)
if typ.Kind() != reflect.Struct {
return fmt.Sprintf("%v", v)
}
var parts []string
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
value := val.Field(i)
// json 태그 읽기
name := field.Tag.Get("json")
if name == "" {
name = field.Name
}
var valueStr string
switch value.Kind() {
case reflect.String:
valueStr = fmt.Sprintf(`"%s"`, value.String())
case reflect.Int, reflect.Int64:
valueStr = fmt.Sprintf("%d", value.Int())
case reflect.Bool:
valueStr = fmt.Sprintf("%t", value.Bool())
default:
valueStr = fmt.Sprintf("%v", value.Interface())
}
parts = append(parts, fmt.Sprintf(`"%s":%s`, name, valueStr))
}
return "{" + strings.Join(parts, ",") + "}"
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
func main() {
user := User{"Alice", 30, "alice@example.com"}
json := SimpleJSON(user)
fmt.Println(json)
// {"name":"Alice","age":30,"email":"alice@example.com"}
}
2. 구조체 검증기
import "strings"
func Validate(v interface{}) error {
val := reflect.ValueOf(v)
typ := reflect.TypeOf(v)
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
value := val.Field(i)
// required 태그 확인
if field.Tag.Get("validate") == "required" {
if isZero(value) {
return fmt.Errorf("field %s is required", field.Name)
}
}
}
return nil
}
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.String:
return v.String() == ""
case reflect.Int, reflect.Int64:
return v.Int() == 0
case reflect.Bool:
return !v.Bool()
default:
return false
}
}
type User struct {
Name string `validate:"required"`
Email string `validate:"required"`
Age int
}
func main() {
// 유효한 경우
user1 := User{Name: "Alice", Email: "alice@example.com", Age: 30}
if err := Validate(user1); err != nil {
fmt.Println(err)
} else {
fmt.Println("Valid") // Valid
}
// 유효하지 않은 경우
user2 := User{Name: "Bob"}
if err := Validate(user2); err != nil {
fmt.Println(err) // field Email is required
}
}
3. 구조체 복사
func CopyStruct(src, dst interface{}) error {
srcVal := reflect.ValueOf(src)
dstVal := reflect.ValueOf(dst)
if dstVal.Kind() != reflect.Ptr {
return fmt.Errorf("dst must be a pointer")
}
dstVal = dstVal.Elem()
if srcVal.Kind() != reflect.Struct || dstVal.Kind() != reflect.Struct {
return fmt.Errorf("both must be structs")
}
srcType := srcVal.Type()
for i := 0; i < srcVal.NumField(); i++ {
srcField := srcType.Field(i)
srcValue := srcVal.Field(i)
dstField := dstVal.FieldByName(srcField.Name)
if dstField.IsValid() && dstField.CanSet() {
if srcValue.Type() == dstField.Type() {
dstField.Set(srcValue)
}
}
}
return nil
}
type Source struct {
Name string
Age int
Email string
}
type Destination struct {
Name string
Age int
Phone string
}
func main() {
src := Source{Name: "Alice", Age: 30, Email: "alice@example.com"}
dst := Destination{}
CopyStruct(src, &dst)
fmt.Printf("%+v\n", dst) // {Name:Alice Age:30 Phone:}
}
4. 맵을 구조체로 변환
func MapToStruct(m map[string]interface{}, result interface{}) error {
val := reflect.ValueOf(result)
if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
return fmt.Errorf("result must be a pointer to struct")
}
val = val.Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
if !fieldVal.CanSet() {
continue
}
// 맵에서 값 찾기
mapKey := strings.ToLower(field.Name)
mapValue, exists := m[mapKey]
if !exists {
continue
}
// 타입 변환
mapVal := reflect.ValueOf(mapValue)
if mapVal.Type().ConvertibleTo(fieldVal.Type()) {
fieldVal.Set(mapVal.Convert(fieldVal.Type()))
}
}
return nil
}
type Person struct {
Name string
Age int
City string
}
func main() {
m := map[string]interface{}{
"name": "Alice",
"age": 30,
"city": "Seoul",
}
var p Person
MapToStruct(m, &p)
fmt.Printf("%+v\n", p) // {Name:Alice Age:30 City:Seoul}
}
5. 함수 타입 검사
func InspectFunc(f interface{}) {
t := reflect.TypeOf(f)
if t.Kind() != reflect.Func {
fmt.Println("Not a function")
return
}
fmt.Printf("Function: %v\n", t)
// 입력 파라미터
fmt.Printf("Inputs (%d):\n", t.NumIn())
for i := 0; i < t.NumIn(); i++ {
fmt.Printf(" %d: %v\n", i, t.In(i))
}
// 출력 파라미터
fmt.Printf("Outputs (%d):\n", t.NumOut())
for i := 0; i < t.NumOut(); i++ {
fmt.Printf(" %d: %v\n", i, t.Out(i))
}
// 가변 인자
fmt.Printf("Variadic: %v\n", t.IsVariadic())
}
func Add(a, b int) int {
return a + b
}
func Printf(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
func main() {
InspectFunc(Add)
// Function: func(int, int) int
// Inputs (2):
// 0: int
// 1: int
// Outputs (1):
// 0: int
// Variadic: false
fmt.Println()
InspectFunc(Printf)
// Function: func(string, ...interface {})
// Inputs (2):
// 0: string
// 1: []interface {}
// Outputs (0):
// Variadic: true
}
6. 제네릭 슬라이스 필터
func Filter(slice interface{}, predicate interface{}) interface{} {
sliceVal := reflect.ValueOf(slice)
if sliceVal.Kind() != reflect.Slice {
return nil
}
predicateVal := reflect.ValueOf(predicate)
if predicateVal.Kind() != reflect.Func {
return nil
}
resultType := sliceVal.Type()
result := reflect.MakeSlice(resultType, 0, 0)
for i := 0; i < sliceVal.Len(); i++ {
elem := sliceVal.Index(i)
// predicate 호출
args := []reflect.Value{elem}
retVals := predicateVal.Call(args)
if len(retVals) > 0 && retVals[0].Bool() {
result = reflect.Append(result, elem)
}
}
return result.Interface()
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 짝수만 필터링
evens := Filter(numbers, func(n int) bool {
return n%2 == 0
})
fmt.Println(evens) // [2 4 6 8 10]
// 문자열 필터링
words := []string{"apple", "banana", "apricot", "cherry"}
aWords := Filter(words, func(s string) bool {
return strings.HasPrefix(s, "a")
})
fmt.Println(aWords) // [apple apricot]
}
7. 타입별 처리 디스패처
type Handler struct {
handlers map[reflect.Type]func(interface{})
}
func NewHandler() *Handler {
return &Handler{
handlers: make(map[reflect.Type]func(interface{})),
}
}
func (h *Handler) Register(example interface{}, handler func(interface{})) {
t := reflect.TypeOf(example)
h.handlers[t] = handler
}
func (h *Handler) Handle(v interface{}) {
t := reflect.TypeOf(v)
if handler, exists := h.handlers[t]; exists {
handler(v)
} else {
fmt.Printf("No handler for type: %v\n", t)
}
}
type Message struct {
Text string
}
type Command struct {
Name string
}
func main() {
handler := NewHandler()
// 타입별 핸들러 등록
handler.Register(Message{}, func(v interface{}) {
msg := v.(Message)
fmt.Printf("Message received: %s\n", msg.Text)
})
handler.Register(Command{}, func(v interface{}) {
cmd := v.(Command)
fmt.Printf("Command executed: %s\n", cmd.Name)
})
// 처리
handler.Handle(Message{Text: "Hello"}) // Message received: Hello
handler.Handle(Command{Name: "Start"}) // Command executed: Start
handler.Handle("unknown") // No handler for type: string
}
8. 구조체 비교
func DeepEqual(a, b interface{}) bool {
valA := reflect.ValueOf(a)
valB := reflect.ValueOf(b)
return deepEqual(valA, valB)
}
func deepEqual(a, b reflect.Value) bool {
if !a.IsValid() || !b.IsValid() {
return a.IsValid() == b.IsValid()
}
if a.Type() != b.Type() {
return false
}
switch a.Kind() {
case reflect.Struct:
for i := 0; i < a.NumField(); i++ {
if !deepEqual(a.Field(i), b.Field(i)) {
return false
}
}
return true
case reflect.Slice, reflect.Array:
if a.Len() != b.Len() {
return false
}
for i := 0; i < a.Len(); i++ {
if !deepEqual(a.Index(i), b.Index(i)) {
return false
}
}
return true
case reflect.Map:
if a.Len() != b.Len() {
return false
}
for _, key := range a.MapKeys() {
if !deepEqual(a.MapIndex(key), b.MapIndex(key)) {
return false
}
}
return true
default:
return a.Interface() == b.Interface()
}
}
type Person struct {
Name string
Age int
Friends []string
}
func main() {
p1 := Person{
Name: "Alice",
Age: 30,
Friends: []string{"Bob", "Carol"},
}
p2 := Person{
Name: "Alice",
Age: 30,
Friends: []string{"Bob", "Carol"},
}
p3 := Person{
Name: "Alice",
Age: 31,
Friends: []string{"Bob", "Carol"},
}
fmt.Println(DeepEqual(p1, p2)) // true
fmt.Println(DeepEqual(p1, p3)) // false
}
일반적인 실수
1. nil 체크 누락
// ❌ 나쁜 예 (nil 패닉)
func GetType(v interface{}) reflect.Type {
return reflect.TypeOf(v) // v가 nil이면 nil 반환
}
func main() {
t := GetType(nil)
fmt.Println(t.Name()) // 패닉!
}
// ✅ 좋은 예 (nil 체크)
func GetType(v interface{}) reflect.Type {
t := reflect.TypeOf(v)
if t == nil {
return nil
}
return t
}
func main() {
t := GetType(nil)
if t != nil {
fmt.Println(t.Name())
}
}
2. CanSet 확인 누락
// ❌ 나쁜 예 (패닉)
func main() {
x := 42
v := reflect.ValueOf(x)
v.SetInt(100) // 패닉: reflect: reflect.Value.SetInt using unaddressable value
}
// ✅ 좋은 예 (CanSet 확인)
func main() {
x := 42
v := reflect.ValueOf(&x).Elem()
if v.CanSet() {
v.SetInt(100)
fmt.Println(x) // 100
}
}
3. 타입 변환 오류
// ❌ 나쁜 예 (패닉)
func main() {
v := reflect.ValueOf("hello")
n := v.Int() // 패닉: reflect: call of reflect.Value.Int on string Value
}
// ✅ 좋은 예 (Kind 확인)
func main() {
v := reflect.ValueOf("hello")
switch v.Kind() {
case reflect.String:
fmt.Println(v.String())
case reflect.Int:
fmt.Println(v.Int())
}
}
4. 포인터 역참조 누락
// ❌ 나쁜 예 (구조체 필드 접근 실패)
type Person struct {
Name string
}
func main() {
p := &Person{Name: "Alice"}
v := reflect.ValueOf(p)
// 패닉: reflect: call of reflect.Value.NumField on ptr Value
for i := 0; i < v.NumField(); i++ {
// ...
}
}
// ✅ 좋은 예 (Elem으로 역참조)
func main() {
p := &Person{Name: "Alice"}
v := reflect.ValueOf(p).Elem()
for i := 0; i < v.NumField(); i++ {
fmt.Println(v.Field(i))
}
}
5. 성능 고려 부족
// ❌ 나쁜 예 (반복문에서 리플렉션)
func Sum(items interface{}) int {
sum := 0
v := reflect.ValueOf(items)
for i := 0; i < v.Len(); i++ {
// 매번 리플렉션 사용
sum += int(v.Index(i).Int())
}
return sum
}
// ✅ 좋은 예 (타입 단언 사용)
func Sum(items interface{}) int {
// 타입 단언으로 직접 처리
if nums, ok := items.([]int); ok {
sum := 0
for _, n := range nums {
sum += n
}
return sum
}
// 리플렉션은 최후의 수단
return sumReflect(items)
}
func sumReflect(items interface{}) int {
// 리플렉션 로직...
return 0
}
6. unexported 필드 접근
type person struct {
name string // unexported
Age int // exported
}
// ❌ 나쁜 예 (unexported 필드 수정 시도)
func main() {
p := person{name: "Alice", Age: 30}
v := reflect.ValueOf(&p).Elem()
nameField := v.FieldByName("name")
// 패닉: reflect: reflect.Value.SetString using value obtained using unexported field
nameField.SetString("Bob")
}
// ✅ 좋은 예 (exported 필드만 사용)
type Person struct {
Name string // exported
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(&p).Elem()
nameField := v.FieldByName("Name")
if nameField.CanSet() {
nameField.SetString("Bob")
fmt.Println(p) // {Bob 30}
}
}
7. 메소드 호출 인자 오류
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
// ❌ 나쁜 예 (잘못된 인자)
func main() {
calc := Calculator{}
v := reflect.ValueOf(calc)
method := v.MethodByName("Add")
// 패닉: reflect: Call with too few input arguments
method.Call([]reflect.Value{reflect.ValueOf(10)})
}
// ✅ 좋은 예 (올바른 인자)
func main() {
calc := Calculator{}
v := reflect.ValueOf(calc)
method := v.MethodByName("Add")
// 올바른 개수의 인자
args := []reflect.Value{
reflect.ValueOf(10),
reflect.ValueOf(20),
}
result := method.Call(args)
fmt.Println(result[0].Int()) // 30
}
베스트 프랙티스
1. 타입 스위치 우선
// ✅ 리플렉션보다 타입 스위치 우선
func Process(v interface{}) {
switch val := v.(type) {
case int:
fmt.Println("Integer:", val)
case string:
fmt.Println("String:", val)
case []int:
fmt.Println("Int slice:", val)
default:
// 마지막 수단으로 리플렉션
processReflect(v)
}
}
2. 에러 처리
// ✅ 안전한 리플렉션 래퍼
func SafeSet(ptr interface{}, value interface{}) error {
v := reflect.ValueOf(ptr)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("ptr must be a pointer")
}
v = v.Elem()
if !v.CanSet() {
return fmt.Errorf("value cannot be set")
}
newVal := reflect.ValueOf(value)
if !newVal.Type().AssignableTo(v.Type()) {
return fmt.Errorf("type mismatch: %v vs %v", newVal.Type(), v.Type())
}
v.Set(newVal)
return nil
}
3. 캐싱 활용
// ✅ 타입 정보 캐싱
var typeCache sync.Map
func GetCachedType(v interface{}) reflect.Type {
t := reflect.TypeOf(v)
if cached, ok := typeCache.Load(t); ok {
return cached.(reflect.Type)
}
typeCache.Store(t, t)
return t
}
4. 문서화
// ✅ 명확한 문서화
// SetField sets the value of a struct field by name.
// ptr must be a pointer to a struct.
// Returns an error if the field doesn't exist or cannot be set.
func SetField(ptr interface{}, fieldName string, value interface{}) error {
v := reflect.ValueOf(ptr)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("ptr must be a pointer to struct")
}
field := v.Elem().FieldByName(fieldName)
if !field.IsValid() {
return fmt.Errorf("field %s not found", fieldName)
}
if !field.CanSet() {
return fmt.Errorf("field %s cannot be set", fieldName)
}
newVal := reflect.ValueOf(value)
if !newVal.Type().AssignableTo(field.Type()) {
return fmt.Errorf("type mismatch")
}
field.Set(newVal)
return nil
}
5. 벤치마크
// ✅ 성능 측정
func BenchmarkReflect(b *testing.B) {
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
b.Run("Direct", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = p.Name
}
})
b.Run("Reflect", func(b *testing.B) {
v := reflect.ValueOf(p)
for i := 0; i < b.N; i++ {
_ = v.FieldByName("Name").String()
}
})
}
6. IsValid 확인
// ✅ IsValid 체크
func GetFieldValue(v interface{}, fieldName string) (interface{}, error) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
field := val.FieldByName(fieldName)
if !field.IsValid() {
return nil, fmt.Errorf("field %s not found", fieldName)
}
return field.Interface(), nil
}
7. 제네릭 헬퍼
// ✅ 제네릭 유틸리티
func IsNil(v interface{}) bool {
if v == nil {
return true
}
val := reflect.ValueOf(v)
switch val.Kind() {
case reflect.Ptr, reflect.Map, reflect.Slice,
reflect.Chan, reflect.Func, reflect.Interface:
return val.IsNil()
}
return false
}
func main() {
var p *int
var m map[string]int
fmt.Println(IsNil(p)) // true
fmt.Println(IsNil(m)) // true
fmt.Println(IsNil(42)) // false
}
8. 테스트
// ✅ 리플렉션 코드 테스트
func TestSetField(t *testing.T) {
type Person struct {
Name string
Age int
}
tests := []struct {
name string
ptr interface{}
fieldName string
value interface{}
wantErr bool
}{
{
name: "valid string field",
ptr: &Person{},
fieldName: "Name",
value: "Alice",
wantErr: false,
},
{
name: "invalid field",
ptr: &Person{},
fieldName: "NotExists",
value: "test",
wantErr: true,
},
{
name: "type mismatch",
ptr: &Person{},
fieldName: "Name",
value: 123,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := SetField(tt.ptr, tt.fieldName, tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("wantErr %v, got %v", tt.wantErr, err)
}
})
}
}
정리
- 기본: TypeOf (타입), ValueOf (값), Kind (카테고리), Elem (역참조)
- 구조체: 필드 순회, 태그 읽기, 값 수정, 중첩 접근
- 메소드: 목록 조회, 동적 호출, 리시버 처리
- 실전: JSON 직렬화, 검증기, 구조체 복사, 맵 변환, 함수 검사, 제네릭 필터, 디스패처, 비교
- 실수: nil 체크, CanSet 확인, 타입 변환, 포인터 역참조, 성능, unexported 필드, 인자 오류
- 베스트: 타입 스위치 우선, 에러 처리, 캐싱, 문서화, 벤치마크, IsValid 확인, 제네릭 헬퍼, 테스트