Go JSON 처리 방법과 구조체 매핑 (encoding/json) – 업무에 사용하는 Go 언어 응용편 5

Go json
Go json

웹 개발에서 JSON은 데이터 교환의 표준 형식이 되었습니다. Go 언어로 웹 애플리케이션을 개발할 때 go JSON 처리는 필수적인 기능이며, Go의 내장 패키지인 encoding/json을 통해 효율적으로 처리할 수 있습니다.

JSON 기본 개념과 Go에서의 중요성

JSON(JavaScript Object Notation)은 경량의 데이터 교환 형식으로, 사람이 읽고 쓰기 쉬우며 기계가 파싱하고 생성하기도 용이합니다. Go json 처리는 웹 API 개발, 설정 파일 읽기, 데이터베이스 연동 등 다양한 상황에서 활용됩니다.

필자는 go언어를 활용하여 웹 사이트를 구성한 경험이 있으며, 그 과정에서 JSON 처리의 중요성을 깨달았습니다. 특히 클라이언트와 서버 간 데이터 통신에서 JSON 형식을 올바르게 처리하지 못하면 전체 시스템이 제대로 작동하지 않을 수 있습니다.

구조체 정의와 JSON 태그

Go에서 JSON을 처리하기 위해서는 먼저 데이터 구조를 정의해야 합니다. 구조체(struct)를 사용하여 JSON 데이터의 형태를 미리 정의하고, JSON 태그를 통해 필드 매핑을 설정할 수 있습니다.

Go
package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type User struct {
    ID       int       `json:"id"`
    Name     string    `json:"name"`
    Email    string    `json:"email"`
    Age      int       `json:"age"`
    IsActive bool      `json:"is_active"`
    CreatedAt time.Time `json:"created_at"`
}

위 예시에서 각 필드에 붙은 json:"필드명" 태그는 JSON 키와 Go 구조체 필드 간의 매핑을 정의합니다. 이를 통해 JSON의 snake_case 표기법과 Go의 PascalCase 표기법을 자연스럽게 연결할 수 있습니다.

구조체 필드의 첫 글자는 반드시 대문자로 시작해야 하며, 이는 해당 필드를 외부 패키지에서 접근 가능하게 만듭니다. json 패키지가 이 필드들에 접근하여 직렬화/역직렬화를 수행하기 때문입니다.

Go json 마샬링(Marshaling) 과정

마샬링은 Go 구조체를 JSON 문자열로 변환하는 과정입니다. encoding/json 패키지의 Marshal 함수를 사용하여 구현할 수 있습니다.

Go
func main() {
    user := User{
        ID:       1,
        Name:     "김철수",
        Email:    "kim@example.com",
        Age:      30,
        IsActive: true,
        CreatedAt: time.Now(),
    }

    jsonData, err := json.Marshal(user)
    if err != nil {
        fmt.Printf("마샬링 에러: %v\n", err)
        return
    }

    fmt.Printf("JSON 결과: %s\n", string(jsonData))
}

이 코드는 User 구조체 인스턴스를 생성하고, json.Marshal 함수를 통해 JSON 바이트 배열로 변환합니다. 결과는 다음과 같은 형태의 JSON 문자열이 됩니다:

Go
{
    "id": 1,
    "name": "김철수",
    "email": "kim@example.com",
    "age": 30,
    "is_active": true,
    "created_at": "2024-01-15T10:30:00Z"
}

json.Marshal 함수는 ([]byte, error) 타입을 반환하므로, 에러 처리를 반드시 해야 합니다. 마샬링 과정에서 발생할 수 있는 오류로는 순환 참조, 지원하지 않는 타입 등이 있습니다.

JSON 언마샬링(Unmarshaling)과 데이터 파싱

언마샬링은 JSON 문자열을 Go 구조체로 변환하는 역과정입니다. 웹 API에서 받은 JSON 데이터를 Go 애플리케이션에서 사용할 수 있는 형태로 변환할 때 활용됩니다.

Go
func parseJSONExample() {
    jsonString := `{
        "id": 2,
        "name": "이영희",
        "email": "lee@example.com",
        "age": 25,
        "is_active": false,
        "created_at": "2024-01-20T15:45:00Z"
    }`

    var user User
    err := json.Unmarshal([]byte(jsonString), &user)
    if err != nil {
        fmt.Printf("언마샬링 에러: %v\n", err)
        return
    }

    fmt.Printf("파싱된 사용자: %+v\n", user)
    fmt.Printf("이름: %s, 나이: %d\n", user.Name, user.Age)
}

json.Unmarshal 함수는 JSON 바이트 배열과 대상 구조체의 포인터를 받아서 데이터를 파싱합니다. 함수의 두 번째 인자로 구조체의 포인터(&user)를 전달하는 것이 중요합니다. 이를 통해 함수가 실제 구조체 인스턴스를 수정할 수 있습니다.

파싱 과정에서 JSON 키와 일치하지 않는 구조체 필드는 무시되며, 구조체에 정의되지 않은 JSON 키 역시 무시됩니다. 이러한 특성은 API 버전 호환성을 유지하는 데 도움이 됩니다.

고급 JSON 태그 활용법

JSON 태그는 단순한 필드 매핑 외에도 다양한 옵션을 제공합니다. 이를 통해 더 세밀한 제어가 가능합니다.

Go
type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name"`
    Price       float64 `json:"price"`
    Description string  `json:"description,omitempty"`
    InternalID  string  `json:"-"`
    Category    string  `json:"category,omitempty"`
    Stock       *int    `json:"stock,omitempty"`
}

위 예시에서 사용된 태그 옵션들을 살펴보면:

  • omitempty: 필드가 빈 값(zero value)일 때 JSON 출력에서 제외
  • "-": 해당 필드를 JSON 처리에서 완전히 제외
  • 포인터 타입(*int): nil 값을 허용하여 선택적 필드 구현

필자는 추후에 gin 웹 프레임워크를 통해 고도화된 사이트를 만들었는데, 이때 omitempty 태그가 매우 유용했습니다. 클라이언트에게 불필요한 빈 필드를 전송하지 않음으로써 네트워크 트래픽을 줄이고 JSON 응답을 더 깔끔하게 만들 수 있었습니다.

중첩 구조체와 복합 JSON 처리

실제 애플리케이션에서는 단순한 구조체보다는 중첩된 구조체를 다루는 경우가 많습니다. go언어 json 처리에서는 이러한 복잡한 구조도 자연스럽게 처리할 수 있습니다.

Go
type Address struct {
    Street   string `json:"street"`
    City     string `json:"city"`
    PostCode string `json:"post_code"`
}

type Company struct {
    Name    string  `json:"name"`
    Address Address `json:"address"`
}

type Employee struct {
    ID       int     `json:"id"`
    Name     string  `json:"name"`
    Company  Company `json:"company"`
    Skills   []string `json:"skills"`
    Projects []Project `json:"projects,omitempty"`
}

type Project struct {
    Name        string `json:"name"`
    Description string `json:"description"`
    Completed   bool   `json:"completed"`
}

이러한 중첩 구조체는 복잡한 JSON 데이터를 체계적으로 관리할 수 있게 해줍니다. 예를 들어, 직원 정보에 회사 정보와 주소가 포함된 JSON을 처리할 때 각각의 논리적 단위를 별도의 구조체로 분리하여 코드의 가독성과 유지보수성을 높일 수 있습니다.

에러 처리와 디버깅 전략

go언어 json 처리에서 발생할 수 있는 다양한 에러 상황과 이를 효과적으로 처리하는 방법을 알아보겠습니다.

Go
func robustJSONProcessing() {
    invalidJSON := `{
        "id": "not_a_number",
        "name": "테스트",
        "age": 25
    }`

    var user User
    err := json.Unmarshal([]byte(invalidJSON), &user)
    if err != nil {
        if syntaxErr, ok := err.(*json.SyntaxError); ok {
            fmt.Printf("JSON 구문 오류 위치: %d\n", syntaxErr.Offset)
        } else if typeErr, ok := err.(*json.UnmarshalTypeError); ok {
            fmt.Printf("타입 불일치: %s 필드에 %s 타입 값을 %s 타입으로 변환할 수 없음\n", 
                typeErr.Field, typeErr.Value, typeErr.Type)
        } else {
            fmt.Printf("알 수 없는 오류: %v\n", err)
        }
        return
    }

    fmt.Printf("성공적으로 파싱됨: %+v\n", user)
}

이 예시에서는 JSON 파싱 시 발생할 수 있는 구체적인 에러 타입을 확인하고 적절한 처리를 수행합니다. json.SyntaxError는 잘못된 JSON 구문을 나타내며, json.UnmarshalTypeError는 타입 불일치를 나타냅니다.

성능 최적화와 실무 활용 팁

Go json 처리 성능을 최적화하기 위한 몇 가지 실무 팁을 공유하겠습니다. 대용량 JSON 데이터를 처리할 때는 json.Decoder와 json.Encoder를 활용하는 것이 메모리 효율적입니다.

Go
func streamingJSONProcessing() {
    jsonStream := `
    {"id": 1, "name": "사용자1"}
    {"id": 2, "name": "사용자2"}
    {"id": 3, "name": "사용자3"}
    `

    decoder := json.NewDecoder(strings.NewReader(jsonStream))
    
    for {
        var user User
        if err := decoder.Decode(&user); err == io.EOF {
            break
        } else if err != nil {
            fmt.Printf("디코딩 에러: %v\n", err)
            continue
        }
        
        fmt.Printf("처리된 사용자: %+v\n", user)
    }
}

스트리밍 방식은 메모리 사용량을 크게 줄일 수 있으며, 특히 대용량 JSON 파일이나 연속적인 JSON 데이터 스트림을 처리할 때 유용합니다.

필자가 실제 프로젝트에서 경험한 바로는, JSON 처리 성능이 중요한 경우 구조체 태그를 최적화하고 불필요한 필드를 제거하는 것만으로도 상당한 성능 향상을 얻을 수 있었습니다. 또한 JSON 데이터의 크기가 클 때는 압축을 활용하거나 필요한 필드만 선택적으로 처리하는 방식을 고려해볼 만합니다.

이상으로 Go 언어에서 JSON 처리 방법과 구조체 매핑에 대해 살펴보았습니다. encoding/json 패키지의 강력한 기능들을 활용하면 웹 애플리케이션 개발에서 데이터 처리를 효율적으로 수행할 수 있습니다.