메뉴

문서정보

목차

Go 언어와 태그

Go 언어에서 json을 다루다 보면 아래와 같은 구조체를 볼 수 있을 거다.
type User struct {
    Name string `json:"name,omitempty"`
    Age  int    `json:"age,omitempty"`
}
GoLang의 구조체(struct)의 필드는 Tag를 이용해서 메타정보를 추가하는 것으로 의미를 확장할 수 있다. 위의 구조체에서 Name과 Age 필드는 각각 json 필드와 매핑되며, nil이 허용(omitempt) 될 수 있음을 의미한다. 이 정보는 Golang의 encoding/json 패키지가 json 데이터를 마샬/언마샬(marshal, unmarshal) 할 때 사용한다. 이 문서에서 태그를 어디에 어떻게 사용 할 수 있는지 살펴볼 것이다.

GoLang struct

Go언어에서 구조체는 일련의 필드들로 구성된다. 각 필드들은 다시 이름(name)과 타입(type)으로 구성된다. 아래 코드를 보자.
package main

import "fmt"

type T1 struct {
    f1 string
}

type T2 struct {
    T1
    f2     int64
    f3, f4 float64
}

func main() {
    fmt.Println("vim-go")
    t := T2{T1{"foo"}, 1, 2, 3}
    fmt.Println(t.f1)
    fmt.Println(t.T1.f1)
    fmt.Println(t.f2)
}

T2 구조체의 T1 필드는 임베디드(embedded)필드로 이름이 선언되지 않는 필드를 의미한다. 이외에 F2는 f2, f3, f4 3개의 필드들을 더 가지고 있다. 세미콜론을 사용 할 경우에 하나의 라인에 같은 타입의 필드들을 선언 할 수 있다.

Tag

각 필드는 옵션으로 태그를 필드 속성으로 설정 할 수 있다. 아래 코드를 보자.
package main

import (
    "fmt"
    "reflect"
)

type T1 struct {
    f1     string  "f one"
    f2     int64   `f two`
    f3, f4 float64 `f three and four`
}

func main() {
    t := reflect.TypeOf(T1{})
    f1, _ := t.FieldByName("f1")
    fmt.Println("T1.f1 infomation")
    fmt.Println("Tag ", f1.Tag)
    fmt.Println("Name", f1.Name)
    fmt.Println("Type", f1.Type)

    fmt.Println("========")
    f2, _ := t.FieldByName("f2")
    fmt.Println("T1.f2 infomation")
    fmt.Println("Tag ", f2.Tag)
    fmt.Println("Name", f2.Name)
    fmt.Println("Type", f2.Type)

}

실행해보자.
T1.f1 infomation
Tag  f one
Name f1
Type string
========
T1.f2 infomation
Tag  f two
Name f2
Type int64
Go는 reflect를 이용해서 프로그램의 런타임에 변수와 값을 검사하고 타입을 체크하고 구조체의 메타정보를 가져올 수 있다. Tag도 구조체의 메타정보 중 하나다. 위의 예제에서 T1 구조체에 있는 각 변수의 Tag 값을 읽어오는 것을 확인 할 수 있다.

Tag의 사용

Tag는 보통 Key1:"value" key2:"value" 와 같은 key/value 목록의 형식으로 작성한다. 아래는 Go의 ORM 라이브러리인 Gorm 에서의 스트럭쳐 선언이다.
type User struct {
    gorm.Model
    Name         string
    Age          sql.NullInt64
    Birthday     *time.Time
    Email        string  `gorm:"type:varchar(100);unique_index"`
    Role         string  `gorm:"size:255"`    
    MemberNumber *string `gorm:"unique;not null"`
    Num          int     `gorm:"AUTO_INCREMENT"`          
    Address      string  `gorm:"index:addr"`              
    IgnoreMe     int     `gorm:"-"`                            
}                                                              
Gorm은 스트럭처의 Tag를 읽어서 정확한 데이터베이스 작업을 수행 할 수 있을 것이다.

Tag는 reflex의 Lookup과 Get 메서드로 사용 할 수 있다. 아래 코드를 보자.
package main

import (
    "fmt"
    "reflect"
)

type T struct {
    f string `one:"1" two:"2" size:"100"`
}

func main() {
    t := reflect.TypeOf(T{})
    f, _ := t.FieldByName("f")
    fmt.Println(f.Tag)            // one:"1" two:"2" size:"100"

    v, ok := f.Tag.Lookup("one")
    fmt.Printf("%s %t\n", v, ok)  // 1 true

    v, ok = f.Tag.Lookup("size")
    fmt.Printf("%s %t\n", v, ok)  // 100 true

    v, ok = f.Tag.Lookup("blank") // false
    fmt.Printf("%s %t\n", v, ok) 
}
Get 메서드는 Lookup의 간단한 wrapper 함수로 boolean flag를 무시한다.
func (tag StructTag) Get(key string) string {
    v, _ := tag.Lookup(key)
    return v
}

reflect를 이용해서 범용적으로 사용할 수 있을 만한 예제 코드를 만들어봤다.
package main

import (
    "fmt"
    "reflect"
    "time"

    "database/sql"
    "github.com/jinzhu/gorm"
)

type User struct {
    gorm.Model
    Name         string
    Age          sql.NullInt64
    Birthday     *time.Time
    Email        string  `gorm:"type:varchar(100);unique_index"`
    Role         string  `gorm:"size:255"`
    MemberNumber *string `gorm:"unique;not null"`
    Num          int     `gorm:"AUTO_INCREMENT"`
    Address      string  `gorm:"index:addr"`
    IgnoreMe     int     `gorm:"-"`
}

func main() {
    t := reflect.TypeOf(User{})
    for i := 0; i < t.NumField(); i++ {
        v, ok := t.Field(i).Tag.Lookup("gorm")
        if ok {
            fmt.Printf("(%14s) is ORM Field : %s\n", t.Field(i).Name, v)
        }
    }
}

실행결과다.
(         Email) is ORM Field : type:varchar(100);unique_index
(          Role) is ORM Field : size:255
(  MemberNumber) is ORM Field : unique;not null
(           Num) is ORM Field : AUTO_INCREMENT
(       Address) is ORM Field : index:addr
(      IgnoreMe) is ORM Field : -
이제 ORM을 처리하는 패키지는 구조체에서 key가 "gorm"인 필드를 읽을 수 있을 것이다. 이 필드는 ORM처리를 하라는 의미이미로 value를 읽어서 적당한 처리를 하면 된다.

예를들어 구조체로 부터 테이블을 Create하는 메서드라면, addr 인덱스를 만들고, Num int 필드는 AUTO_INCREMENT인 sql문을 만들어서 실행 할 수 있을 것이다.
CREATE TABLE user (
	Num          int auto_increment,
	Email        VARCHAR(100),
	Role         VARCHAR(255),
	Address      text,  
	MemberNumber text NOT NULL,
	index        addr(ADDRESS),
	UNIQUE KEY(Email)
);

Multi Tag

Go언어는 multi tag를 허용한다. 구조체를 여러 개의 형태로 마샬링 할 필요가 있을 때 사용한다. API의 반환 값 형태를 파라미터에 따라서 JSON 혹은 XML로 리턴해야 하는 경우가 대표적인 예다.
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name    string `json:"name" xml:"name"`
    Address string `json:"address" xml:"address"`
    Age     string `json:"age"`
}

func main() {
    t := reflect.TypeOf(User{})
    for i := 0; i < t.NumField(); i++ {
        v, ok := t.Field(i).Tag.Lookup("json")
        if ok {
            fmt.Printf("(%14s) is Json Field : %s\n", t.Field(i).Name, v)
        }
        v, ok = t.Field(i).Tag.Lookup("xml")
        if ok {
            fmt.Printf("(%14s) is xml Field : %s\n", t.Field(i).Name, v)
        }
    }
}

실행결과
(          Name) is Json Field : name
(          Name) is xml Field : name
(       Address) is Json Field : address
(       Address) is xml Field : address
(           Age) is Json Field : age

Tag를 사용하는 패키지들

Marshal/UnMarshal 패키지들이 대부분이다.

정리