
Go 언어 JSON 태그 완전 정복: omitempty부터 커스텀 태그까지
Go 언어로 API 서버나 웹 애플리케이션을 개발하다 보면, JSON 데이터를 다루는 일은 피할 수 없는 숙명과도 같습니다.
이때 Go의 struct와 JSON 데이터를 서로 변환하는 과정, 즉 직렬화(Serialization, 마샬링)와 역직렬화(Deserialization, 언마샬링)에서 'struct tag'는 아주 중요하고 강력한 역할을 수행합니다.
오늘은 이 json 태그를 사용하여 어떻게 JSON 데이터를 정교하게 제어할 수 있는지, 기본부터 고급 활용법까지 함께 정복해 보겠습니다.
왜 JSON 태그가 필요할까?
가장 먼저, 왜 이 태그가 필요한지부터 알아야 합니다.
Go 언어에서는 외부 패키지에서 접근할 수 있는 필드(public field)의 이름을 대문자로 시작하는 '파스칼 케이스(PascalCase)'로 작성하는 것이 관례입니다.
하지만 대부분의 JSON API에서는 키 이름을 소문자로 시작하는 '카멜 케이스(camelCase)'나 '스네이크 케이스(snake_case)'로 사용하는 것이 일반적입니다.
만약 아무런 처리 없이 Go 구조체를 JSON으로 변환하면 어떻게 될까요?
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
FirstName string
Age int
}
func main() {
p := Person{FirstName: "Alice", Age: 30}
jsonData, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
}
위 코드를 실행하면 아래와 같은 결과가 나옵니다.
{"FirstName":"Alice","Age":30}
Go 구조체의 필드 이름이 그대로 JSON의 키 이름이 되었습니다.
이것은 우리가 원하는 형식이 아닐 수 있습니다.
바로 이 문제를 해결해 주는 것이 'json' 태그입니다.
기본 사용법: JSON 키 이름 지정하기
struct 필드 옆에 역따옴표(`)로 감싼 태그를 추가하여, JSON으로 변환될 때 사용할 키 이름을 직접 지정할 수 있습니다.
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
이제 각 필드는 태그에 명시된 이름으로 매핑됩니다.
Name필드는 JSON에서"name"키에 해당합니다.Age필드는"age"키에 해당합니다.Email필드는"email"키에 해당하며,omitempty라는 추가 옵션이 붙어있습니다.
(이 옵션은 잠시 후에 자세히 알아보겠습니다.
)
JSON 태그의 핵심 옵션들
json 태그는 단순히 키 이름을 바꾸는 것 외에도 여러 유용한 옵션을 제공합니다.
1. 필드 무시하기: json:"-"
구조체의 특정 필드를 JSON으로 변환하고 싶지 않을 때가 있습니다.
예를 들어, 비밀번호나 내부적으로만 사용하는 값 같은 경우죠.
이때는 태그 값으로 -를 사용하면 됩니다.
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"-"` // Password 필드는 JSON 변환 시 완전히 무시됩니다.
}
이제 User 구조체를 JSON으로 마샬링해도 Password 필드는 결과물에 포함되지 않으며, JSON 데이터를 User 구조체로 언마샬링할 때도 Password 필드는 무시됩니다.
2. 빈 값 생략하기: omitempty
omitempty 옵션은 필드의 값이 해당 타입의 '제로 값(zero value)'일 경우, JSON 결과물에서 해당 필드를 아예 생략하도록 만듭니다.
Go에서 타입별 제로 값은 다음과 같습니다.
- 숫자 타입 (int, float64 등):
0 string:""(빈 문자열)bool:false- 포인터, 인터페이스, 슬라이스, 맵 등:
nil
type Profile struct {
Name string `json:"name"`
Age int `json:"age,omitempty"` // Age가 0이면 생략됩니다.
Email *string `json:"email,omitempty"` // Email이 nil이면 생략됩니다.
Tags []string `json:"tags,omitempty"` // Tags가 nil이거나 비어있으면 생략됩니다.
}
이 옵션은 선택적인 필드를 다룰 때 매우 유용합니다.
클라이언트에게 불필요한 null이나 빈 값을 보내지 않아 JSON 응답을 더 깔끔하게 만들 수 있습니다.
주의할 점: 만약 0이나 false가 유효한 값인데도 생략되면 안 되는 경우가 있습니다.
예를 들어, '재고 수량' 필드의 값이 실제로 0일 수 있죠.
이런 경우에는 omitempty를 사용하지 않거나, 해당 필드를 포인터 타입(*int)으로 선언하여 nil일 때만 생략되도록 만드는 방법을 사용해야 합니다.
3. 값을 문자열로 변환하기: string
때로는 숫자나 불리언 값을 JSON에서 문자열 형태로 표현해야 할 때가 있습니다.
특히 JavaScript를 사용하는 클라이언트와 통신할 때, 아주 큰 정수(JavaScript의 Number.MAX_SAFE_INTEGER를 넘는)가 손실되는 것을 방지하기 위해 문자열로 전달하는 경우가 대표적입니다.
이때 string 옵션을 사용합니다.
type Product struct {
ID int64 `json:"id,string"` // ID를 JSON에서 "12345" 형태의 문자열로 변환합니다.
Name string `json:"name"`
Active bool `json:"active,string"` // Active를 "true" 또는 "false" 문자열로 변환합니다.
}
고급 활용: 여러 태그와 리플렉션(Reflection)
Go의 struct tag는 json만을 위한 것이 아닙니다.
하나의 필드에 여러 목적의 태그를 동시에 추가할 수 있습니다.
type Person struct {
Name string `json:"name" xml:"name" db:"user_name"`
Age int `json:"age" xml:"age" db:"user_age"`
}
위 예시처럼 JSON, XML, 데이터베이스 ORM 등 다양한 라이브러리가 각자의 태그를 읽어 사용하도록 하나의 구조체를 정의할 수 있습니다.
그렇다면 Go는 이 태그들을 어떻게 읽어내는 걸까요?
바로 '리플렉션(Reflection)'이라는 기능을 사용합니다.
리플렉션은 프로그램 실행 중에 코드의 타입 정보나 구조를 분석하고 수정할 수 있게 해주는 기능입니다.
import (
"fmt"
"reflect"
)
func main() {
t := reflect.TypeOf(Person{})
field, _ := t.FieldByName("Name")
jsonTag := field.Tag.Get("json")
xmlTag := field.Tag.Get("xml")
dbTag := field.Tag.Get("db")
fmt.Printf("JSON Tag: %s\n", jsonTag) // 출력: JSON Tag: name
fmt.Printf("XML Tag: %s\n", xmlTag) // 출력: XML Tag: name
fmt.Printf("DB Tag: %s\n", dbTag) // 출력: DB Tag: user_name
}
encoding/json 패키지 내부에서도 이와 같은 리플렉션 기술을 사용하여 우리가 정의한 json 태그를 읽고, 그에 맞춰 데이터를 변환하는 것입니다.
결론
Go 구조체의 JSON 태그를 이해하고 활용하는 것은 Go 애플리케이션과 다른 시스템 간의 원활한 데이터 교환을 위한 필수적인 기술입니다.
단순한 키 이름 변경부터 빈 값 처리, 타입 변환에 이르기까지, 태그를 통해 우리는 JSON 직렬화 및 역직렬화 과정을 매우 정밀하게 제어할 수 있습니다.
이는 결국 더 깨끗하고, 예측 가능하며, 효율적인 프로그램을 만드는 밑거름이 될 것입니다.
더 상세한 예제나 고급 사용법이 궁금하다면, 공식 Go 문서를 참고하는 것이 가장 좋은 방법입니다.
'Go' 카테고리의 다른 글
| Go 언어의 심장을 파헤치다 추상 구문 트리(AST) 완벽 가이드 (0) | 2025.07.13 |
|---|---|
| Go 언어 난수 생성 완벽 가이드: math/rand부터 crypto/rand까지 (0) | 2025.07.12 |
| Go 제네릭 완전 정복: interface{} 시대의 종말과 새로운 패러다임 (0) | 2025.07.12 |
| Go 언어의 청소부, 가비지 컬렉터 파헤치기: 메모리 관리, 이젠 맡겨주세요! (0) | 2025.06.03 |
| Go 언어에 클래스가 없다고? 걱정 마세요! 구조체와 인터페이스로 다 됩니다! (0) | 2025.06.03 |