Comment ne pas marshaler une structure vide en JSON avec Go?

88

J'ai une structure comme celle-ci:

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

Mais même si l'instance de MyStruct est entièrement vide (ce qui signifie que toutes les valeurs sont par défaut), elle est sérialisée comme:

"data":{}

Je sais que les documents encoding / json spécifient que les champs «vides» sont:

false, 0, tout pointeur ou valeur d'interface nil, et tout tableau, tranche, carte ou chaîne de longueur zéro

mais sans considération pour une structure avec toutes les valeurs vides / par défaut. Tous ses champs sont également étiquetés omitempty, mais cela n'a aucun effet.

Comment puis-je obtenir le package JSON pour ne pas marshaler mon champ qui est une structure vide?

Mat
la source

Réponses:

137

Comme le disent les documents, "tout pointeur nul". - faire de la structure un pointeur. Pointeurs ont des valeurs évidentes « vides »: nil.

Fix - définissez le type avec un champ de pointeur struct :

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

Puis une valeur comme celle-ci:

result := Result{}

Will marshal comme:

{}

Explication: Notez le *MyStructdans notre définition de type. La sérialisation JSON ne se soucie pas de savoir s'il s'agit d'un pointeur ou non - c'est un détail d'exécution. Ainsi, transformer des champs struct en pointeurs n'a des implications que pour la compilation et l'exécution).

Notez simplement que si vous changez le type de champ de MyStructà *MyStruct, vous aurez besoin de pointeurs pour structurer les valeurs pour le remplir, comme ceci:

Data: &MyStruct{ /* values */ }
Mat
la source
2
Bless you Matt, c'est ce que je cherchais
Venkata SSKM Chaitanya
@Matt, êtes-vous sûr que cela &MyStruct{ /* values */ }compte comme un pointeur nul? La valeur n'est pas nulle.
Shuzheng le
@Matt Est-il possible de faire ce comportement par défaut? Je veux toujours omettre. (fondamentalement, n'utilisez pas la balise dans chaque champ de toutes les structures)
Mohit Singh
17

Comme @chakrit mentionné dans un commentaire, vous ne pouvez pas obtenir ce travail en mettant en place json.Marshalersur MyStructet la mise en œuvre d' une fonction de rassemblement JSON personnalisé sur chaque struct qui l' utilise peut être beaucoup plus de travail. Cela dépend vraiment de votre cas d'utilisation pour savoir si cela vaut le travail supplémentaire ou si vous êtes prêt à vivre avec des structures vides dans votre JSON, mais voici le modèle que j'utilise appliqué à 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
}

Si vous avez d'énormes structures avec de nombreux champs, cela peut devenir fastidieux, en particulier en changeant l'implémentation d'une structure plus tard, mais à moins de réécrire l'ensemble du jsonpackage pour répondre à vos besoins (ce n'est pas une bonne idée), c'est à peu près la seule façon dont je peux penser à obtenir ceci fait tout en gardant un non-pointeur MyStructlà - dedans.

De plus, vous n'avez pas besoin d'utiliser des structures en ligne, vous pouvez en créer des nommées. J'utilise LiteIDE avec la complétion de code, donc je préfère en ligne pour éviter l'encombrement.

Leylandski
la source
9

Dataest une structure initialisée, elle n'est donc pas considérée comme vide car elle encoding/jsonne regarde que la valeur immédiate, pas les champs à l'intérieur de la structure.

Malheureusement, le retour nilde json.Marhslerne fonctionne pas actuellement:

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

Vous pouvez également donner Resultun marshaleur, mais cela n'en vaut pas la peine.

La seule option, comme le suggère Matt, est de créer Dataun pointeur et de définir la valeur sur nil.

Luke
la source
1
Je ne vois pas pourquoi je ne encoding/json peux pas vérifier les champs enfants de la structure. Ce ne serait pas très efficace, oui. Mais ce n'est certainement pas impossible.
nemo
@nemo Je vois votre point, j'ai changé le libellé. Il ne le fait pas parce que ce ne serait pas efficace. Cela peut être fait au json.Marshalercas par cas.
Luke
2
Il n'est pas possible de décider s'il MyStructest vide ou non en implémentant un json.Marshalersur MyStructlui-même. Preuve: play.golang.org/p/UEC8A3JGvx
chakrit
Pour ce faire, vous devrez implémenter json.Marshalersur le Resulttype conteneur lui-même, ce qui pourrait être très gênant.
chakrit
3

Il existe une proposition exceptionnelle de Golang pour cette fonctionnalité qui est active depuis plus de 4 ans, donc à ce stade, il est prudent de supposer qu'elle ne sera pas intégrée à la bibliothèque standard de si tôt. Comme @Matt l'a souligné, l' approche traditionnelle consiste à convertir les structures en pointeurs vers les structures . Si cette approche est irréalisable (ou peu pratique), une alternative consiste à utiliser un autre encodeur json qui prend en charge l' omission de structures à valeur nulle .

J'ai créé un miroir de la bibliothèque Golang json ( clarketm / json ) avec un support supplémentaire pour omettre les structures à valeur nulle lorsque la omitemptybalise est appliquée. Cette bibliothèque détecte l' absence de zéron d'une manière similaire au populaire encodeur YAML go-yaml en vérifiant récursivement les champs de structure publique .

par exemple

$ 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
la source