Wie kann man mit Go keine leere Struktur in JSON marshallen?

87

Ich habe eine Struktur wie diese:

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

Aber selbst wenn die Instanz von MyStruct vollständig leer ist (dh alle Werte sind Standardwerte), wird sie wie folgt serialisiert:

"data":{}

Ich weiß, dass die Kodierungs- / JSON- Dokumente angeben, dass "leere" Felder sind:

false, 0, ein beliebiger Nullzeiger oder Schnittstellenwert sowie ein beliebiges Array, Slice, Map oder String der Länge Null

aber ohne Berücksichtigung einer Struktur mit allen leeren / Standardwerten. Alle Felder sind ebenfalls mit gekennzeichnet omitempty, dies hat jedoch keine Auswirkung.

Wie kann ich das JSON-Paket dazu bringen , mein Feld, das eine leere Struktur ist , nicht zu marshallen?

Matt
quelle

Antworten:

132

Wie die Dokumente sagen, "jeder Nullzeiger". - Machen Sie die Struktur zu einem Zeiger. Zeiger haben offensichtliche "leere" Werte : nil.

Fix - definieren den Typ mit einer Struktur Zeigerfeld:

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

Dann ein Wert wie dieser:

result := Result{}

Wird als: Marschall:

{}

Erläuterung: Beachten Sie die *MyStructin unserer Typdefinition. Bei der JSON-Serialisierung ist es egal, ob es sich um einen Zeiger handelt oder nicht - das ist ein Laufzeitdetail. Das Erstellen von Strukturfeldern zu Zeigern hat also nur Auswirkungen auf das Kompilieren und die Laufzeit.

Beachten Sie nur, dass Sie, wenn Sie den Feldtyp von MyStructauf ändern *MyStruct, Zeiger auf Strukturwerte benötigen, um ihn wie folgt zu füllen:

Data: &MyStruct{ /* values */ }
Matt
quelle
2
Segne dich Matt, das ist was ich gesucht habe
Venkata SSKM Chaitanya
@Matt, bist du sicher, dass das &MyStruct{ /* values */ }als Nullzeiger zählt? Der Wert ist nicht Null.
Shuzheng
@Matt Ist es möglich, dieses Standardverhalten festzulegen? Ich möchte immer weglassen. (Verwenden Sie das Tag grundsätzlich nicht in jedem Feld aller Strukturen)
Mohit Singh
14

Wie @chakrit in einem Kommentar erwähnt, können Sie diese an der Arbeit nicht erhalten durch die Umsetzung json.Marshalerauf MyStruct, und auf jeder Struktur eine benutzerdefinierten JSON Rangier Funktion implementiert , dass Verwendungen es viel mehr Arbeit sein können. Es hängt wirklich von Ihrem Anwendungsfall ab, ob sich die zusätzliche Arbeit lohnt oder ob Sie bereit sind, mit leeren Strukturen in Ihrem JSON zu leben, aber hier ist das Muster, das ich verwende 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
}

Wenn Sie große Strukturen mit vielen Feldern haben, kann dies mühsam werden, insbesondere wenn Sie die Implementierung einer Struktur später ändern. Wenn Sie jedoch jsonnicht das gesamte Paket neu schreiben , um es Ihren Anforderungen anzupassen (keine gute Idee), ist dies so ziemlich der einzige Weg, den ich mir vorstellen kann Dies geschah, während immer noch ein Nicht-Zeiger MyStructdrin blieb.

Außerdem müssen Sie keine Inline-Strukturen verwenden, sondern können benannte erstellen. Ich verwende LiteIDE jedoch mit Code-Vervollständigung, daher bevorzuge ich Inline, um Unordnung zu vermeiden.

Leylandski
quelle
9

Dataist eine initialisierte Struktur, daher wird sie nicht als leer betrachtet, da encoding/jsonnur der unmittelbare Wert und nicht die Felder in der Struktur betrachtet werden.

Leider funktioniert die Rückkehr nilvon json.Marhslerderzeit nicht:

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

Sie könnten auch Resulteinen Marschall geben , aber die Mühe lohnt sich nicht.

Die einzige Möglichkeit, wie Matt vorschlägt, besteht darin, Dataeinen Zeiger zu erstellen und den Wert auf zu setzen nil.

Luke
quelle
1
Ich verstehe nicht, warum die untergeordneten Felder der Struktur encoding/json nicht überprüft werden können. Es wäre nicht sehr effizient, ja. Aber es ist sicherlich nicht unmöglich.
Nemo
@nemo Ich verstehe deinen Standpunkt, ich habe den Wortlaut geändert. Es macht es nicht, weil es nicht effizient wäre. Dies kann jedoch json.Marshalervon Fall zu Fall erfolgen.
Luke
2
Es ist nicht möglich zu entscheiden, ob MyStructes leer ist oder nicht, indem ein json.Marshaleron on MyStructselbst implementiert wird . Beweis: play.golang.org/p/UEC8A3JGvx
Chakrit
Dazu müssten Sie json.Marshalerden enthaltenen ResultTyp selbst implementieren , was sehr unpraktisch sein könnte.
Chakrit
2

Es gibt einen hervorragenden Golang- Vorschlag für diese Funktion, der seit über 4 Jahren aktiv ist. Daher kann an dieser Stelle davon ausgegangen werden, dass sie nicht in Kürze in die Standardbibliothek aufgenommen wird. Wie @Matt hervorhob, besteht der traditionelle Ansatz darin, die Strukturen in Zeiger in Strukturen umzuwandeln . Wenn dieser Ansatz nicht durchführbar (oder unpraktisch) ist, besteht eine Alternative darin, einen alternativen JSON-Encoder zu verwenden, der das Weglassen von Nullwertstrukturen unterstützt .

Ich habe einen Spiegel der Golang json-Bibliothek ( clarketm / json ) erstellt, der zusätzlich unterstützt, dass beim Anwenden des omitemptyTags Nullwertstrukturen weggelassen werden . Diese Bibliothek erkennt die Nullheit auf ähnliche Weise wie der beliebte YAML-Encoder go-yaml, indem sie die öffentlichen Strukturfelder rekursiv überprüft .

z.B

$ 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
quelle