Menu

메모용 개발 블로그

전체보기 > Develop > Go >

Go 구조체 리터널 태그 정리

2022-11-01 23:16:35

공부 목적 겸 개인 프로젝트로 Go 언어를 사용하다가 구조체를 json으로 변경할 일이 생겼다.

이 때 Go언어의 특성 상 대/소문자를 제약하여야 하는데. 이 경우 json에서 key의 값을 지정하는 과정 중 독특한 문법을 보게 되었다.

type Json struct {
	Ok   bool   `json:"ok"`
	Data string `json:"data"`
}

상당히 편리하고 직관적이게 지정할 수 있었다.

과연 이것이 무언인지 궁금해서 찾아보고 이를 정리하게 되었다.

리터널 태그란?

간단하게 설명하면 구조체의 각 필드에 문자열로 된 태그를 정의할 수 있는 기능이다.

go의 스펙 문서는 다음에 위치하고 있다. 해당 단락 하단부에 나온다.

https://go.dev/ref/spec#Struct_types

리터널 태그 가져오기

대충 훑어보면 Tag에 대한 설명과 이에 대한 접근은 Reflection 인터페이스를 통해서 접근해볼 수 있는 모양이다.

https://pkg.go.dev/reflect

위 문서 상단에 보면 TypeOf를 호출하여 정보를 가져올 수 있다.

reflect.TypeOf(Json{})

TypeOf 문서 : https://pkg.go.dev/reflect#TypeOf

위와 같이 가져오게 되면 리턴으로 Type 형식이 반환된다.

Type 문서 : https://pkg.go.dev/reflect#Type

이제 필드에 접근을 하여야 하므로 가장 간단하게 이름을 기반으로 필드를 찾아보도록 하겠다.

FieldByName(name string) (StructField, bool)

FieldByName을 통해서 구조체 필드를 가져올 수 있다.

그 이후 반환된 StructField 객체에서 필드의 이름, 타입, 태그 등을 가져올 수 있다.

package main

import (
	"fmt"
	"reflect"
)

type Json struct {
	Ok   bool   `json:"ok"`
	Data string `json:"data"`
}

func main() {
	t := reflect.TypeOf(Json{})

	f, _ := t.FieldByName("Ok")

	fmt.Println(f.Tag)
}

위 코드를 실행하면 다음 결과가 나온다.

json:"ok"

그러면 이제 이걸 알아서 잘 파싱해서 활용하면 되는가? 아니다.

이미 다 준비가 되어 있다.

Tag는 StructTag인데. 문서를 참고하자. https://pkg.go.dev/reflect#StructTag

Lookup을 활용하면 된다.

func (tag StructTag) Lookup(key string) (value string, ok bool)

리턴으로 값과 존재여부를 리턴하는데. 리턴 형이 두개면 번거로우므로 이를 생략해주는 간단한 Wrapper도 이미 마련해놓았다.

Get

func (tag StructTag) Get(key string) string

https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/reflect/type.go;l=1183

위 링크를 가서 확인해보면 확인 가능하다.

예제 1 - Get으로 태그 정보 가져오기

package main

import (
	"fmt"
	"reflect"
)

type Json struct {
	Ok   bool   `json:"ok"`
	Data string `json:"data"`
}

func main() {
	t := reflect.TypeOf(Json{})

	f, _ := t.FieldByName("Ok")

	fmt.Println(f.Tag.Get("json"))
}

최종으로 활용한 코드이다. 실행하면 다음과 같이 출력된다.

ok

예제 2 - Lookup으로 태그 정보 가져오기

lookup을 통해서 태그 정보를 가져오는 코드이다. 여기서 없는 xml 태그를 가져오는 것도 포함되어 있다.

태그가 없을 경우 ok에 false가 리턴되는 모습을 볼 수 있다.

package main

import (
	"fmt"
	"reflect"
)

type Json struct {
	Ok   bool   `json:"ok"`
	Data string `json:"data"`
}

func main() {
	t := reflect.TypeOf(Json{})

	f, _ := t.FieldByName("Ok")
	v, ok := f.Tag.Lookup("json")

	fmt.Println(v)
	fmt.Println(ok)

	v2, ok2 := f.Tag.Lookup("xml")
	fmt.Println(v2)
	fmt.Println(ok2)
}

출력

ok
true

false

3. 멀티 태그

여러 태그를 달 수도 있다.

package main

import (
	"fmt"
	"reflect"
)

type Json struct {
	Ok   bool   `json:"ok" xml:"XmlOk"`
	Data string `json:"data"`
}

func main() {
	t := reflect.TypeOf(Json{})

	f, _ := t.FieldByName("Ok")
	v, ok := f.Tag.Lookup("json")

	fmt.Println(v)
	fmt.Println(ok)

	v2, ok2 := f.Tag.Lookup("xml")
	fmt.Println(v2)
	fmt.Println(ok2)
}

출력

ok
true
XmlOk
true

정상적으로 가져오는 모습을 볼 수 있다.

마치며

실제로 태그를 사용하는 무언가를 구현한다기 보다는 구현되어 있는 것을 활용할 일이 많을 것 이므로 몰라도 쓰는데 지장은 없다.

그래도 사용하다보니 참으로 편리하고 깔끔한 기능구현이 되다보니 이게 무엇인가.. 싶은 마음에 찾아보고 정리해보았다.