PHpullh
학습 라이브러리/Go/리플렉션 기초 — reflect 패키지

GO · 패턴

리플렉션 기초 — reflect 패키지

reflect 패키지는 런타임에 타입 정보를 조회하고 값을 동적으로 조작합니다. ORM, JSON 직렬화, DI 프레임워크 등 범용 라이브러리 개발에 필수적이지만, 성능과 타입 안전성을 트레이드오프합니다.

패턴고급reflectreflectionruntimestruct-tagsdynamic

핵심 설명

reflect 패키지는 런타임에 타입 정보를 조회하고 값을 동적으로 조작합니다. ORM, JSON 직렬화, DI 프레임워크 등 범용 라이브러리 개발에 필수적이지만, 성능과 타입 안전성을 트레이드오프합니다.

Go code

package main

import (
	"fmt"
	"reflect"
	"strings"
)

type User struct {
	Name  string  // json:"name" validate:"required"
	Email string  // json:"email" validate:"required,email"
	Age   int     // json:"age" validate:"min=0,max=150"
}
// 참고: 실제 코드에서는 백틱 태그를 사용합니다
// Name string `json:"name" validate:"required"`

// 1. 타입 정보 조회
func inspectType(v interface{}) {
	t := reflect.TypeOf(v)
	fmt.Printf("타입: %s, 종류: %s\n", t.Name(), t.Kind())

	if t.Kind() == reflect.Struct {
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i)
			tag := f.Tag.Get("json")
			validate := f.Tag.Get("validate")
			fmt.Printf("  필드: %-6s 타입: %-6s json:%s validate:%s\n",
				f.Name, f.Type, tag, validate)
		}
	}
}

// 2. 동적 값 조작
func setField(obj interface{}, fieldName string, value interface{}) error {
	v := reflect.ValueOf(obj)
	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("포인터가 필요합니다")
	}
	field := v.Elem().FieldByName(fieldName)
	if !field.IsValid() {
		return fmt.Errorf("필드 '%s'를 찾을 수 없습니다", fieldName)
	}
	if !field.CanSet() {
		return fmt.Errorf("필드 '%s'를 설정할 수 없습니다", fieldName)
	}
	val := reflect.ValueOf(value)
	if field.Type() != val.Type() {
		return fmt.Errorf("타입 불일치: %s != %s", field.Type(), val.Type())
	}
	field.Set(val)
	return nil
}

// 3. 간단한 구조체 → Map 변환 (JSON 태그 기반)
func structToMap(v interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	val := reflect.ValueOf(v)
	typ := reflect.TypeOf(v)

	for i := 0; i < val.NumField(); i++ {
		field := typ.Field(i)
		jsonTag := field.Tag.Get("json")
		if jsonTag == "" || jsonTag == "-" {
			continue
		}
		key := strings.Split(jsonTag, ",")[0]
		result[key] = val.Field(i).Interface()
	}
	return result
}

func main() {
	user := User{Name: "홍길동", Email: "hong@go.dev", Age: 30}

	// 타입 조회
	fmt.Println("=== 타입 정보 ===")
	inspectType(user)

	// 동적 값 설정
	fmt.Println("\n=== 동적 값 변경 ===")
	fmt.Printf("변경 전: %+v\n", user)
	setField(&user, "Name", "김철수")
	setField(&user, "Age", 25)
	fmt.Printf("변경 후: %+v\n", user)

	// 구조체 → Map
	fmt.Println("\n=== Map 변환 ===")
	m := structToMap(user)
	for k, v := range m {
		fmt.Printf("  %s: %v\n", k, v)
	}
}

학습 팁

리플렉션은 일반 코드보다 10~100배 느립니다. 핫 패스(hot path)에서는 피하고, 초기화 시점에 리플렉션 결과를 캐싱하거나 코드 생성(go generate)을 대안으로 고려하세요.

주의할 점

미내보내 필드(소문자)는 reflect.Value.Set()으로 변경할 수 없습니다. CanSet()으로 먼저 확인하지 않으면 패닉이 발생합니다.

자주 묻는 질문

리플렉션 기초 — reflect 패키지란 무엇인가요?

reflect 패키지는 런타임에 타입 정보를 조회하고 값을 동적으로 조작합니다. ORM, JSON 직렬화, DI 프레임워크 등 범용 라이브러리 개발에 필수적이지만, 성능과 타입 안전성을 트레이드오프합니다.

리플렉션 기초 — reflect 패키지 학습 시 주의할 점은 무엇인가요?

미내보내 필드(소문자)는 reflect.Value.Set() 으로 변경할 수 없습니다. CanSet() 으로 먼저 확인하지 않으면 패닉이 발생합니다.