JSON · XML · CSV · base64 · gob — 인코딩 완전 정복
데이터를 저장하거나 전송할 때 직렬화(serialization)가 필요합니다. Go 표준 라이브러리는 가장 흔한 포맷을 모두 지원합니다.
encoding/json — 가장 많이 쓰는 인코딩
기본 Marshal/Unmarshal
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
Country string `json:"country"`
ZipCode string `json:"zip_code,omitempty"` // 빈 값이면 필드 생략
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age,omitempty"` // 0이면 생략
Password string `json:"-"` // 항상 생략
Tags []string `json:"tags"`
Address Address `json:"address"`
IsActive bool `json:"is_active"`
}
func main() {
user := User{
ID: 1,
Name: "Alice",
Email: "alice@example.com",
Age: 30,
Password: "secret123",
Tags: []string{"admin", "developer"},
Address: Address{
City: "Seoul",
Country: "Korea",
},
IsActive: true,
}
// 구조체 → JSON (Marshal)
data, err := json.Marshal(user)
if err != nil {
fmt.Println("Marshal 오류:", err)
return
}
fmt.Println(string(data))
// {"id":1,"name":"Alice","email":"alice@example.com","age":30,"tags":["admin","developer"],"address":{"city":"Seoul","country":"Korea"},"is_active":true}
// 들여쓰기 포함 (MarshalIndent)
pretty, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(pretty))
// JSON → 구조체 (Unmarshal)
jsonStr := `{
"id": 2,
"name": "Bob",
"email": "bob@example.com",
"tags": ["user"],
"address": {"city": "Busan", "country": "Korea", "zip_code": "48000"},
"is_active": false
}`
var decoded User
if err := json.Unmarshal([]byte(jsonStr), &decoded); err != nil {
fmt.Println("Unmarshal 오류:", err)
return
}
fmt.Printf("디코딩된 사용자: %+v\n", decoded)
}
스트리밍 인코더/디코더
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
// NewEncoder — io.Writer에 직접 JSON 쓰기
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
products := []Product{
{1, "Go 교재", 29900},
{2, "Go 키보드", 89000},
{3, "Go 머그컵", 15000},
}
for _, p := range products {
if err := encoder.Encode(p); err != nil {
fmt.Println("인코딩 오류:", err)
}
}
// NewDecoder — io.Reader에서 JSON 읽기
jsonLines := `{"id":1,"name":"사과","price":1500}
{"id":2,"name":"바나나","price":800}
{"id":3,"name":"체리","price":5000}`
decoder := json.NewDecoder(strings.NewReader(jsonLines))
for decoder.More() { // 더 읽을 JSON이 있으면 true
var p Product
if err := decoder.Decode(&p); err != nil {
fmt.Println("디코딩 오류:", err)
break
}
fmt.Printf("상품: %s (%.0f원)\n", p.Name, p.Price)
}
}
커스텀 MarshalJSON / UnmarshalJSON
package main
import (
"encoding/json"
"fmt"
"time"
)
// CustomTime — 커스텀 시간 타입
type CustomTime struct {
time.Time
}
// MarshalJSON — JSON 직렬화 커스터마이즈
func (ct CustomTime) MarshalJSON() ([]byte, error) {
return json.Marshal(ct.Format("2006년 01월 02일"))
}
// UnmarshalJSON — JSON 역직렬화 커스터마이즈
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
t, err := time.Parse("2006년 01월 02일", s)
if err != nil {
return err
}
ct.Time = t
return nil
}
type Event struct {
Name string `json:"name"`
StartDate CustomTime `json:"start_date"`
}
func main() {
event := Event{
Name: "Go Conference",
StartDate: CustomTime{time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)},
}
data, _ := json.Marshal(event)
fmt.Println(string(data))
// {"name":"Go Conference","start_date":"2024년 03월 15일"}
var decoded Event
json.Unmarshal(data, &decoded)
fmt.Printf("이벤트: %s, 날짜: %s\n",
decoded.Name, decoded.StartDate.Format("2006-01-02"))
}
json.RawMessage — 지연 파싱
package main
import (
"encoding/json"
"fmt"
)
// APIResponse — 타입에 따라 다른 구조의 데이터
type APIResponse struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"` // 파싱을 나중으로 미룸
}
type UserData struct {
Name string `json:"name"`
Email string `json:"email"`
}
type OrderData struct {
OrderID string `json:"order_id"`
Amount float64 `json:"amount"`
}
func processResponse(resp APIResponse) {
switch resp.Type {
case "user":
var u UserData
json.Unmarshal(resp.Data, &u)
fmt.Printf("사용자: %s (%s)\n", u.Name, u.Email)
case "order":
var o OrderData
json.Unmarshal(resp.Data, &o)
fmt.Printf("주문 %s: %.0f원\n", o.OrderID, o.Amount)
default:
fmt.Printf("알 수 없는 타입: %s\n", resp.Type)
}
}
func main() {
responses := []string{
`{"type":"user","data":{"name":"Alice","email":"alice@example.com"}}`,
`{"type":"order","data":{"order_id":"ORD-001","amount":29900}}`,
}
for _, r := range responses {
var resp APIResponse
json.Unmarshal([]byte(r), &resp)
processResponse(resp)
}
}
encoding/xml — XML 인코딩
package main
import (
"encoding/xml"
"fmt"
)
type Book struct {
XMLName xml.Name `xml:"book"` // XML 요소 이름
ID int `xml:"id,attr"` // XML 속성
Title string `xml:"title"`
Author string `xml:"author"`
Price float64 `xml:"price"`
InStock bool `xml:"in_stock"`
Tags []string `xml:"tags>tag"` // 중첩 요소
}
type Library struct {
XMLName xml.Name `xml:"library"`
Books []Book `xml:"book"`
}
func main() {
library := Library{
Books: []Book{
{
ID: 1,
Title: "The Go Programming Language",
Author: "Alan Donovan",
Price: 45.99,
InStock: true,
Tags: []string{"go", "programming", "systems"},
},
{
ID: 2,
Title: "Go in Action",
Author: "William Kennedy",
Price: 39.99,
InStock: false,
Tags: []string{"go", "web"},
},
},
}
// 구조체 → XML
data, err := xml.MarshalIndent(library, "", " ")
if err != nil {
fmt.Println("Marshal 오류:", err)
return
}
// XML 선언 추가
fmt.Println(xml.Header + string(data))
// XML → 구조체
xmlStr := `<library>
<book id="3">
<title>Learning Go</title>
<author>Jon Bodner</author>
<price>34.99</price>
<in_stock>true</in_stock>
</book>
</library>`
var decoded Library
if err := xml.Unmarshal([]byte(xmlStr), &decoded); err != nil {
fmt.Println("Unmarshal 오류:", err)
return
}
fmt.Printf("첫 번째 책: %s (%.2f달러)\n", decoded.Books[0].Title, decoded.Books[0].Price)
}
encoding/csv — CSV 파일 처리
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
)
type Employee struct {
ID int
Name string
Department string
Salary float64
}
func writeCSV(employees []Employee) error {
file, err := os.Create("employees.csv")
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// 헤더 쓰기
if err := writer.Write([]string{"ID", "이름", "부서", "급여"}); err != nil {
return err
}
// 데이터 쓰기
for _, e := range employees {
record := []string{
strconv.Itoa(e.ID),
e.Name,
e.Department,
strconv.FormatFloat(e.Salary, 'f', 0, 64),
}
if err := writer.Write(record); err != nil {
return err
}
}
return writer.Error()
}
func readCSV(filename string) ([]Employee, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
reader := csv.NewReader(file)
reader.TrimLeadingSpace = true // 앞 공백 제거
// 헤더 건너뛰기
if _, err := reader.Read(); err != nil {
return nil, err
}
// 모든 레코드 읽기
records, err := reader.ReadAll()
if err != nil {
return nil, err
}
var employees []Employee
for _, r := range records {
id, _ := strconv.Atoi(r[0])
salary, _ := strconv.ParseFloat(r[3], 64)
employees = append(employees, Employee{
ID: id, Name: r[1], Department: r[2], Salary: salary,
})
}
return employees, nil
}
func main() {
employees := []Employee{
{1, "Alice", "개발팀", 5500000},
{2, "Bob", "디자인팀", 4800000},
{3, "Charlie", "마케팅팀", 4200000},
}
// CSV 쓰기
if err := writeCSV(employees); err != nil {
fmt.Println("쓰기 오류:", err)
return
}
// CSV 읽기
loaded, err := readCSV("employees.csv")
if err != nil {
fmt.Println("읽기 오류:", err)
return
}
fmt.Println("=== 직원 목록 ===")
for _, e := range loaded {
fmt.Printf("ID: %d, 이름: %s, 부서: %s, 급여: %.0f원\n",
e.ID, e.Name, e.Department, e.Salary)
}
// 인메모리 CSV 파싱 (파일 없이)
csvData := `상품명,가격,재고
사과,1500,100
바나나,800,250
체리,5000,30`
r := csv.NewReader(strings.NewReader(csvData))
r.Read() // 헤더 건너뛰기
for {
record, err := r.Read()
if err != nil {
break // io.EOF
}
fmt.Printf("상품: %s, 가격: %s원\n", record[0], record[1])
}
// 임시 파일 정리
os.Remove("employees.csv")
}
encoding/base64 — 바이너리-텍스트 인코딩
package main
import (
"encoding/base64"
"fmt"
)
func main() {
data := []byte("Hello, Go 세계! 🌍")
// StdEncoding — 표준 Base64 (패딩 '=' 포함)
encoded := base64.StdEncoding.EncodeToString(data)
fmt.Println("표준:", encoded)
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
fmt.Println("디코딩 오류:", err)
return
}
fmt.Println("복원:", string(decoded))
// URLEncoding — URL 안전 Base64 ('+' → '-', '/' → '_')
urlEncoded := base64.URLEncoding.EncodeToString(data)
fmt.Println("URL 안전:", urlEncoded)
// RawStdEncoding — 패딩 없는 Base64
rawEncoded := base64.RawStdEncoding.EncodeToString(data)
fmt.Println("패딩 없음:", rawEncoded)
// RawURLEncoding — URL 안전 + 패딩 없음 (JWT에서 사용)
jwtPart := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
fmt.Println("JWT 헤더:", jwtPart)
// 바이너리 데이터 인코딩 (이미지, 파일 등)
binaryData := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A} // PNG 시그니처
b64 := base64.StdEncoding.EncodeToString(binaryData)
fmt.Printf("PNG 헤더 Base64: %s\n", b64)
}
encoding/gob — Go 네이티브 직렬화
package main
import (
"bytes"
"encoding/gob"
"fmt"
)
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
type GameState struct {
Score int
Level int
Player string
Shapes []Shape
}
func main() {
// 인터페이스를 포함하는 타입을 gob으로 직렬화할 때 타입 등록 필요
gob.Register(Circle{})
gob.Register(Rectangle{})
state := GameState{
Score: 12500,
Level: 5,
Player: "Alice",
Shapes: []Shape{
Circle{Radius: 5},
Rectangle{Width: 10, Height: 3},
},
}
// 직렬화 (Encode)
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf)
if err := encoder.Encode(state); err != nil {
fmt.Println("인코딩 오류:", err)
return
}
fmt.Printf("직렬화된 크기: %d 바이트\n", buf.Len())
// 역직렬화 (Decode)
decoder := gob.NewDecoder(&buf)
var loaded GameState
if err := decoder.Decode(&loaded); err != nil {
fmt.Println("디코딩 오류:", err)
return
}
fmt.Printf("게임 상태: 플레이어=%s, 레벨=%d, 점수=%d\n",
loaded.Player, loaded.Level, loaded.Score)
for _, s := range loaded.Shapes {
fmt.Printf(" 도형 넓이: %.2f\n", s.Area())
}
}
실전 예제 — REST API 응답 직렬화
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// APIError — 표준 에러 응답
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func (e APIError) Error() string {
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
// APIResponse — 표준 성공 응답
type APIResponse[T any] struct {
Success bool `json:"success"`
Data T `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
Timestamp time.Time `json:"timestamp"`
RequestID string `json:"request_id"`
}
func writeJSON(w http.ResponseWriter, statusCode int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(statusCode)
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
encoder.Encode(v)
}
func successResponse[T any](data T, requestID string) APIResponse[T] {
return APIResponse[T]{
Success: true,
Data: data,
Timestamp: time.Now().UTC(),
RequestID: requestID,
}
}
func errorResponse(code int, message, details, requestID string) APIResponse[any] {
return APIResponse[any]{
Success: false,
Error: &APIError{
Code: code,
Message: message,
Details: details,
},
Timestamp: time.Now().UTC(),
RequestID: requestID,
}
}
type UserProfile struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Roles []string `json:"roles"`
JoinedAt string `json:"joined_at"`
}
func main() {
// 성공 응답 시뮬레이션
user := UserProfile{
ID: 42,
Name: "Alice",
Email: "alice@example.com",
Roles: []string{"user", "admin"},
JoinedAt: "2024-01-15",
}
resp := successResponse(user, "req-abc-123")
data, _ := json.MarshalIndent(resp, "", " ")
fmt.Println("=== 성공 응답 ===")
fmt.Println(string(data))
// 에러 응답 시뮬레이션
errResp := errorResponse(404, "사용자를 찾을 수 없습니다",
"ID 999에 해당하는 사용자가 없습니다", "req-def-456")
errData, _ := json.MarshalIndent(errResp, "", " ")
fmt.Println("\n=== 에러 응답 ===")
fmt.Println(string(errData))
// 설정 파일 파서
configJSON := `{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp",
"max_connections": 100
},
"cache": {
"ttl_seconds": 300,
"max_size_mb": 512
},
"feature_flags": {
"dark_mode": true,
"new_ui": false
}
}`
var config map[string]json.RawMessage
json.Unmarshal([]byte(configJSON), &config)
var dbConfig map[string]interface{}
json.Unmarshal(config["database"], &dbConfig)
fmt.Printf("\nDB 호스트: %s, 포트: %.0f\n",
dbConfig["host"], dbConfig["port"])
}
포맷별 비교
| 포맷 | 장점 | 단점 | 적합한 상황 |
|---|---|---|---|
| JSON | 범용성, 가독성 | 크기, 숫자 정밀도 | REST API, 설정 파일 |
| XML | 스키마 지원, 속성 | 장황함, 파싱 복잡 | 레거시 시스템, SOAP |
| CSV | 단순, 스프레드시트 호환 | 타입 없음, 중첩 불가 | 데이터 내보내기/가져오기 |
| Base64 | 바이너리를 텍스트로 | 크기 33% 증가 | JWT, 이미지 임베딩 |
| Gob | Go 네이티브, 빠름 | Go 전용 | 내부 캐시, 프로세스 간 통신 |
프로덕션 환경에서 JSON 성능이 중요하다면 github.com/json-iterator/go나 github.com/bytedance/sonic을 고려하세요. 같은 API를 제공하면서 표준 라이브러리보다 3~8배 빠릅니다.