[json] Go를 사용하여 빈 구조체를 JSON으로 마샬링하지 않는 방법은 무엇입니까?

다음과 같은 구조체가 있습니다.

type Result struct {
    Data       MyStruct  `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

그러나 MyStruct의 인스턴스가 완전히 비어 있어도 (즉, 모든 값이 기본값 임) 다음과 같이 직렬화됩니다.

"data":{}

인코딩 / json 문서가 “빈”필드를 다음과 같이 지정한다는 것을 알고 있습니다.

false, 0, 모든 nil 포인터 또는 인터페이스 값 및 길이가 0 인 모든 배열, 슬라이스, 맵 또는 문자열

그러나 모든 비어있는 / 기본값을 가진 구조체는 고려하지 않습니다. 모든 필드에도으로 태그가 지정 omitempty되지만 아무 효과가 없습니다.

빈 구조체 인 필드를 마샬링 하지 않도록 JSON 패키지를 얻으려면 어떻게 해야합니까?



답변

문서에서 “모든 nil 포인터”라고 말합니다. -구조체를 포인터로 만듭니다. 포인터에는 명백한 “빈”값이 nil있습니다..

수정-구조체 포인터 필드로 유형 정의 :

type Result struct {
    Data       *MyStruct `json:"data,omitempty"`
    Status     string    `json:"status,omitempty"`
    Reason     string    `json:"reason,omitempty"`
}

다음과 같은 값 :

result := Result{}

다음과 같이 마샬링합니다.

{}

설명 : *MyStruct유형 정의에서에 주목하십시오 . JSON 직렬화는 그것이 포인터인지 아닌지 상관하지 않습니다. 이것은 런타임 세부 사항입니다. 따라서 구조체 필드를 포인터로 만드는 것은 컴파일 및 런타임에만 영향을 미칩니다.

필드 유형을에서 MyStruct로 변경하는 경우 다음 *MyStruct과 같이 채우기 위해 값을 구성하는 포인터가 필요합니다.

Data: &MyStruct{ /* values */ }


답변

@chakrit 코멘트에서 언급 한 바와 같이, 당신은 구현하여 작업이 얻을 수 없습니다 json.MarshalerMyStruct, 그리고 사용 그것은 더 많은 작업을 할 수있는 모든 구조체에서 사용자 정의 JSON 마샬링 기능을 구현. 추가 작업의 가치가 있는지 또는 JSON에서 빈 구조체로 살 준비가되었는지 여부에 대한 사용 사례에 따라 다르지만 여기에 적용한 패턴이 있습니다 Result.

type Result struct {
    Data       MyStruct
    Status     string
    Reason     string
}

func (r Result) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    }{
        Data:   &r.Data,
        Status: r.Status,
        Reason: r.Reason,
    })
}

func (r *Result) UnmarshalJSON(b []byte) error {
    decoded := new(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    })
    err := json.Unmarshal(b, decoded)
    if err == nil {
        r.Data = decoded.Data
        r.Status = decoded.Status
        r.Reason = decoded.Reason
    }
    return err
}

많은 필드를 가진 거대한 구조체가 있다면 이것은 지루할 수 있습니다. 특히 구조체의 구현을 나중에 변경하는 것이 지루할 수 있지만, 전체 json패키지를 귀하의 필요에 맞게 재 작성하는 것에는 부족 합니다 (좋은 생각이 아닙니다). 이것은 여전히 ​​비 포인터 MyStruct를 유지하면서 수행됩니다 .

또한 인라인 구조체를 사용할 필요가 없으며 명명 된 구조체를 만들 수 있습니다. 하지만 코드 완성과 함께 LiteIDE를 사용하므로 혼란을 피하기 위해 인라인을 선호합니다.


답변

Data초기화 된 구조체이므로 encoding/json구조체 내부의 필드가 아닌 즉치 값만 확인 하므로 비어있는 것으로 간주 되지 않습니다.

현재 nil에서 돌아 오는 json.Marhsler것은 작동하지 않습니다.

func (_ MyStruct) MarshalJSON() ([]byte, error) {
    if empty {
        return nil, nil // unexpected end of JSON input
    }
    // ...
}

Result마샬 러에게도 줄 수 있지만 노력할 가치가 없습니다.

Matt가 제안한 유일한 옵션 Data은 포인터 를 만들고 값을로 설정하는 것 nil입니다.


답변

4 년 넘게 활성화 된이 기능에 대한 뛰어난 Golang 제안 이 있으므로이 시점에서 곧 표준 라이브러리에 포함되지 않을 것이라고 가정하는 것이 안전합니다. @Matt가 지적했듯이 전통적인 접근 방식은 구조체포인터 대 구조체 로 변환하는 것 입니다. 이 접근 방식이 실행 불가능하거나 비실용적이라면 대안은 0 값 구조체 생략을 지원하는 대체 json 인코더를 사용하는 것 입니다.

태그가 적용될 때 0 값 구조체생략하는 지원이 추가 된 Golang json 라이브러리 ( clarketm / json ) 의 미러를 만들었습니다 . 이 라이브러리를 검출합니다 zeroness 인기 YAML 인코더와 유사한 방식으로 이동-YAML 에 의해 반복적으로 확인하는 공공 구조체 필드를 .omitempty

예 :

$ go get -u "github.com/clarketm/json"
import (
    "fmt"
    "github.com/clarketm/json" // drop-in replacement for `encoding/json`
)

type Result struct {
    Data   MyStruct `json:"data,omitempty"`
    Status string   `json:"status,omitempty"`
    Reason string   `json:"reason,omitempty"`
}

j, _ := json.Marshal(&Result{
    Status: "204",
    Reason: "No Content",
})

fmt.Println(string(j))
// Note: `data` is omitted from the resultant json.
{
  "status": "204"
  "reason": "No Content"
}


답변