reflect 어떻게 쓰지 - 1

Reflect을 가장 먼저 접하게 된것은 작년이었는데 그때 당시엔 그냥 대충 써보다가 어려워서 기존에 사용했었던 코드들에서도 다 reflect를 빼버렸었다. 당장 급했는데 자꾸 뭐시기 뭐시기 나오네 이러고 별로 이해하려는 노력 없이 사용했었기 때문이었던 것같다.

그러다 다시 go를 쓰게 되서 코딩을 하다가 또 reflect를 사용해야하는 경우를 발견했는데,

type sampleJSON struct {
	Num string `json:"NUM" joyful:"true"`
}

뭐 이런식으로 되어있다고 할때, json struct field의 tag를 받아오고 싶을 때 어떻게 할지를 찾아보다가 reflect를 사용하게 되면 쉽게 가능하다는 걸 알게 되었다.

일단 잠시 reflect 맛을 보기 위해 예제를 수행해보자

// 예를 들어 sj라는 이름의 sample Json struct가 있다고 하자
sj := sampleJSON{"01000000000"}
fmt.Println(reflect.ValueOf(sj).Type())
// 1. reflect.ValueOf(interface) 해당 인터페이스의 reflect.Value값이 나온다
// 2. 거기서 Type()을 호출하면 해당 변수의 타입이 나온다. 여기선 sampleJSON
// 3. 해당 타입의 Kind()를 조회해보면 구조체이므로 reflect.Struct이다
if reflect.ValueOf(sj).Type().Kind() == reflect.Struct{
	fmt.Println("say hello to struct")
}

즉 reflect는 특정 interface 값의 Value를 조회할 수 있도록한다. 이 Value를 가지고 여러가지를 해볼 수 있는데 이는 추후에 하도록 하고, 일단 이 Value에 Type()을 조회하면 해당 변수의 값이 아닌 sampleJson 구조체 타입 자체의 정보를 다룰수 있다.

reflect.ValueOf(sj).Type()
// main.sampleJSON
reflect.TypeOf(sj)
// main.sampleJSON

또한 위 두 줄은 같은 역할을 수행하므로 밑에 TypeOf를 사용해도 무방하다.

이제 Json tag를 본격적으로 찾아보려면,

structTag := reflect.TypeOf(sj).Field(0).Tag
fmt.Println(structTag)
// json:"NUM" joyful:"true"
fmt.Println(structTag.Get("joyful"))
// true

위와 같이 하면 되는데, tag는 reflect.StructField 에 저장되있는 속성이므로 TypeOf으로 타입을 조회한 후, Field(특정 필드를 선택)로 StructField 를 받아오면된다. 또한 특정 태그만 받아오고 싶을시 Get을 통해 특정태그만 받아올 수도 있다.

주의할 점은 위에서 설명한건 reflect.ValueOf 또는 TypeOf의 파라미터로 들어온 interface타입이 값일때를 말한다는 것이다. 만약 포인터값일때는

reflect.TypeOf(&sj).Elem().Field(0).Tag

이런식으로 Elem()을 추가해주도록한다. 따라서 요기능을 넣는 함수들에는 해당 값이 값인지 포인터인지 판단하는 로직이 들어가야한다.