Jak nie organizować pustej struktury do JSON za pomocą Go?

88

Mam taką strukturę:

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

Ale nawet jeśli wystąpienie MyStruct jest całkowicie puste (co oznacza, że ​​wszystkie wartości są domyślne), jest serializowane jako:

"data":{}

Wiem, że dokumentacja encoding / json określa, że ​​„puste” pola to:

false, 0, dowolny wskaźnik zerowy lub wartość interfejsu oraz dowolna tablica, wycinek, mapa lub ciąg o długości zero

ale bez uwzględnienia struktury ze wszystkimi pustymi / domyślnymi wartościami. Wszystkie jego pola są również oznaczone tagiem omitempty, ale nie ma to wpływu.

Jak mogę sprawić, aby pakiet JSON nie organizował mojego pola, które jest pustą strukturą?

Matt
źródło

Odpowiedzi:

137

Jak mówią doktorzy, „dowolny wskaźnik zerowy”. - uczyń strukturę wskaźnikiem. Wskaźniki mają oczywiste „pustych” wartości: nil.

Napraw - określ typ za pomocą pola wskaźnika struct :

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

Następnie wartość taka:

result := Result{}

Będzie marszałkiem jako:

{}

Objaśnienie: Zwróć uwagę na *MyStructw naszej definicji typu. Serializacja JSON nie dba o to, czy jest to wskaźnik, czy nie - to szczegół środowiska uruchomieniowego. Tak więc przekształcenie pól strukturalnych w wskaźniki ma wpływ tylko na kompilację i działanie).

Zwróć uwagę, że jeśli zmienisz typ pola z MyStructna *MyStruct, będziesz potrzebować wskaźników do struktur wartości, aby je wypełnić, na przykład:

Data: &MyStruct{ /* values */ }
Matt
źródło
2
Błogosławię cię Matt, właśnie tego szukałem
Venkata SSKM Chaitanya
@Matt, czy na pewno &MyStruct{ /* values */ }liczy się to jako wskaźnik zerowy? Wartość nie jest zerowa.
Shuzheng
@Matt Czy można ustawić to zachowanie domyślne? Chcę zawsze pomijać puste. (w zasadzie nie używaj tagu w każdym polu wszystkich struktur)
Mohit Singh
17

Jak @chakrit wspomniano w komentarzu, nie można uzyskać to do pracy poprzez wdrożenie json.Marshalerna MyStructi wdrożenie funkcji rozrządowej zwyczaj JSON na każdej struktury, które zastosowań może być o wiele więcej pracy. To naprawdę zależy od twojego przypadku użycia, czy jest to warte dodatkowej pracy, czy też jesteś przygotowany do życia z pustymi strukturami w swoim JSON, ale oto wzorzec, którego używam, zastosowany do 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
}

Jeśli masz ogromne struktury z wieloma polami, może to stać się uciążliwe, zwłaszcza późniejsza zmiana implementacji struktury, ale oprócz przepisania całego jsonpakietu do własnych potrzeb (nie jest to dobry pomysł), jest to właściwie jedyny sposób, w jaki mogę wymyślić zrobiono to z jednoczesnym zachowaniem tam, gdzie nie ma wskaźnika MyStruct.

Nie musisz też używać struktur wbudowanych, możesz tworzyć nazwane. Używam jednak LiteIDE z uzupełnianiem kodu, więc wolę inline, aby uniknąć bałaganu.

Leylandski
źródło
9

Datajest zainicjowaną strukturą, więc nie jest uważana za pustą, ponieważ encoding/jsonpatrzy tylko na bezpośrednią wartość, a nie na pola wewnątrz struktury.

Niestety powrót nilz json.Marhslerobecnie nie działa:

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

Mógłbyś też dać Resultmarszałka, ale to nie jest warte wysiłku.

Jedyną opcją, jak sugeruje Matt, jest utworzenie Datawskaźnika i ustawienie wartości na nil.

Łukasz
źródło
1
Nie rozumiem, dlaczego encoding/json nie mogę sprawdzić pól potomnych struktury. To nie byłoby zbyt wydajne, tak. Ale z pewnością nie jest to niemożliwe.
nemo
@nemo Widzę twój punkt widzenia, zmieniłem sformułowanie. Nie robi tego, ponieważ nie byłoby wydajne. Można to jednak zrobić json.Marshalerindywidualnie dla każdego przypadku.
Łukasz
2
To nie możliwe, aby zdecydować, czy pogoda nie MyStructjest pusta poprzez wdrożenie json.Marshalerna MyStructsobie. Dowód: play.golang.org/p/UEC8A3JGvx
chakrit
Aby to zrobić, musiałbyś zaimplementować json.Marshalerna samym Resulttypie zawierającym , co mogłoby być bardzo niewygodne.
chakrit
3

Istnieje znakomita propozycja Golang dla tej funkcji, która jest aktywna od ponad 4 lat, więc w tym momencie można bezpiecznie założyć, że w najbliższym czasie nie trafi ona do standardowej biblioteki. Jak zauważył @Matt, tradycyjnym podejściem jest konwersja struktur na wskaźniki-struktury . Jeśli to podejście jest niewykonalne (lub niepraktyczne), alternatywą jest użycie alternatywnego kodera JSON, który obsługuje pomijanie struktur o zerowej wartości .

Stworzyłem kopię lustrzaną biblioteki Golang json ( clarketm / json ) z dodaną obsługą pomijania struktur o zerowej wartości, gdy omitemptytag jest stosowany. Ta biblioteka wykrywa zeroness w podobny sposób, jak popularny koder YAML go-yaml, poprzez rekurencyjne sprawdzanie publicznych pól struktur .

na przykład

$ 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"
}
Travis Clarke
źródło