web-dev-qa-db-fra.com

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

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 en tant que:

"data":{}

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

false, 0, tout pointeur nul ou valeur d'interface, 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 marqués avec omitempty, mais cela n'a aucun effet.

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

66
Matt

Oh! Solution facile: "tout pointeur nul". - faire de la structure un pointeur.

Réparer:

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

Remarquez le *MyStruct - quand je crée un MyStruct maintenant, je le fais simplement par référence:

myStruct := &MyStruct{ /* values */ }

Et maintenant, le MyStruct "vide" n'est plus marshalé en JSON comme souhaité.

107
Matt

Comme @chakrit l'a mentionné dans un commentaire, vous ne pouvez pas faire fonctionner cela en implémentant json.Marshaler on MyStruct, et l'implémentation d'une fonction de marshalling JSON personnalisée sur chaque structure qui l'utilise peut être beaucoup plus compliquée. 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 la modification de l'implémentation d'une structure plus tard, mais à court de réécriture de l'ensemble du package json pour répondre à vos besoins (ce n'est pas une bonne idée), c'est à peu près le seul façon dont je peux penser à faire cela tout en gardant un non-pointeur MyStruct là-dedans.

De plus, vous n'avez pas à utiliser de structures en ligne, vous pouvez en créer des nommées. Cependant, j'utilise LiteIDE avec l'achèvement du code, donc je préfère en ligne pour éviter l'encombrement.

8
Leylandski

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

Malheureusement retournant nil de json.Marhsler ne fonctionne pas actuellement:

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

Vous pouvez également donner à Result un marshaleur, mais cela ne vaut pas la peine.

La seule option, comme le suggère Matt, est de faire de Data un pointeur et de définir la valeur sur nil.

7
Luke