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ą?

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 */ }

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.


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.


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"
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.