메뉴

문서정보

목차

struct를 marshaling 하는 옳은 방법

많은 Golang 초보자들이 struct를 json으로 마샬링하면서 실수를 하곤한다. 이 문서는 이 문제를 어떻게 해결했는지를 담고 있다.

golang에서 정의된 필드를 사용하지 않는 비어있는 struct를 json으로 마샬링하면, 각 필드가 기본 값을 가진체 마샬링 된다. 때때로 이는 개발자를 혼란스럽게 한다. 아래 코드를 보자.

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string
    Age  int
}

func main() {
    user := User{
        Name: "yundream",
        Age:  40,
    }

    encodedData, _ := json.Marshal(user)
    fmt.Println(string(encodedData))

}
이 코드의 출력 값을 아래와 같을 것이다.
{"Name":"yundream","Age":40}

그럼 아래 코드를 실행해보자.
func main() {
    user1 := User{
        Name: "yundream",
    }

    user2 := User{}

    encodedData, _ := json.Marshal(user1)
    fmt.Println(string(encodedData))

    encodedData, _ = json.Marshal(user2)
    fmt.Println(string(encodedData))

}
아래와 같이 출력 할 것이다.
{"Name":"yundream","Age":0}
{"Name":"","Age":0}

JSON으로 비 구조적 데이터를 처리하려고 한다면, 값이 없는 필드는 출력하지 않아야 할 것이다. 이름과 나이를 포함하는 설문조사의 경우 설문대상자가 나이를 입력하지 않았다면, 데이터에서 제외해야지 0을 입력해서는 안될 것이다.

해결

빈 구조체(Empty structs)

Age를 입력 받지 않았는데 값으로 0이 입력되는 이유는 Go언어가 마샬링을 할 때 해당 데이터 타입의 기본 값을 입력하기 때문이다. Int 타입의 기본 값은 0이므로 0이 입력된다. 빈 구조체(필드를 정의했으나 사용하지 않는)의 경우 Go 언어는 해당 필드를 기본 값으로 선언하고 마샬링을 한다. 따라서 선언하지 않는 Age 필드에 기본 값인 0이 입력된다.

"omitempty" 태그를 이용해서 문제를 해결 할 수 있다. Go 공식문서는 omitempty를 아래와 같이 설명하고 있다.

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

"omitempty" 옵션은 필드에 빈 값이(false, 0, nil 포인터, nil 인터페이스, 빈 배열, 슬라이스, 맵 또는 문자열) 있는 경우 인코딩시 필드를 생략 하도록 지정한다.

User 구조체를 아래와 같이 만들어보자.
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string `json:"name,omitempty"`
    Age  int    `json:"age,omitempty"`
}

func main() {
    user1 := User{
        Name: "yundream",
        Age:  0,
    }

    user2 := User{}

    encodedData, _ := json.Marshal(user1)
    fmt.Println(string(encodedData))

    encodedData, _ = json.Marshal(user2)
    fmt.Println(string(encodedData))

}

실행해보자.
{"name":"yundream"}
{}

예상대로 잘 작동한다. 중첩된 구조체(nested structs)에서도 잘 작동하는지 테스트해보자. 아래 코드를 실행해보자.
package main

import (
    "encoding/json"
    "fmt"
)

type Post struct {
    Title       string `json:"title,omitempty"`
    Description string `json:"description,omitempty"`
}

type User struct {
    Name string `json:"name,omitempty"`
    Age  int    `json:"age,omitempty"`
    Post Post   `json:"post,omitempty"`
}

func main() {
    user := User{
        Name: "yundream",
    }

    encodedData, _ := json.Marshal(user)
    fmt.Println(string(encodedData))
}

실행해보자.
{"name":"yundream","post":{}}

"omitempty" 태그를 사용했음에도 원하는대로 작동하지 않는다. 글을 위로 올려서 omitempty 에 대한 공식설명을 살펴보면 nil pointer에 적용되는 것을 확인 할 수 있다. 아래와 같이 포인터를 사용하도록 코드를 수정했다.
type User struct {
    Name string `json:"name,omitempty"`
    Age  int    `json:"age,omitempty"`
    Post *Post  `json:"post,omitempty"`
}

실행해보자.
{"name":"yundream"}

3레벨 이상의 중첩 구조체

3레벨 이상의 복잡한 구조체는 어떨지 테스트했다.
package main  
  
import (  
    "encoding/json"  
    "fmt"  
)  
  
type Comment struct {  
    Description string `json:"description,omitempty"`  
    Score       int    `json:"score,omitempty"`  
}  
type Post struct {  
    Title       string   `json:"title,omitempty"`  
    Description string   `json:"description,omitempty"`  
    Comment     *Comment `json:"comment,omitempty"`  
}  
  
type User struct {  
    Name string `json:"name,omitempty"`  
    Age  int    `json:"age,omitempty"`  
    Post *Post  `json:"post,omitempty"`  
}  
  
func main() {  
    user := User{  
        Name: "yundream",  
        Post: &Post{  
            Title:       "GoLang",  
            Description: "blah blah....",  
        },  
    }  
  
    encodedData, _ := json.Marshal(user)  
    fmt.Println(string(encodedData))  
}  

실행결과
{
   "post" : {
      "description" : "blah blah....",
      "title" : "GoLang"
   },
   "name" : "yundream"
}