Come non eseguire il marshalling di una struttura vuota in JSON con Go?


91

Ho una struttura come questa:

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

Ma anche se l'istanza di MyStruct è completamente vuota (il che significa che tutti i valori sono predefiniti), viene serializzata come:

"data":{}

So che i documenti di codifica / json specificano che i campi "vuoti" sono:

false, 0, qualsiasi puntatore nullo o valore di interfaccia e qualsiasi array, slice, mappa o stringa di lunghezza zero

ma senza alcuna considerazione per una struttura con tutti i valori vuoti / predefiniti. Anche tutti i suoi campi sono contrassegnati con omitempty, ma ciò non ha alcun effetto.

Come posso fare in modo che il pacchetto JSON non effettui il marshalling del mio campo che è una struttura vuota?

Risposte:


142

Come dicono i documenti, "qualsiasi puntatore nullo". - rendi la struttura un puntatore. Puntatori hanno valori evidenti "vuoti": nil.

Correzione: definire il tipo con un campo del puntatore a struttura :

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

Quindi un valore come questo:

result := Result{}

Marshalling come:

{}

Spiegazione: Notare il *MyStructnella nostra definizione di tipo. La serializzazione JSON non si preoccupa se si tratta di un puntatore o meno: questo è un dettaglio di runtime. Quindi trasformare i campi struct in puntatori ha solo implicazioni per la compilazione e il runtime).

Nota che se modifichi il tipo di campo da MyStructa *MyStruct, avrai bisogno di puntatori ai valori della struttura per popolarlo, in questo modo:

Data: &MyStruct{ /* values */ }

2
Dio ti benedica Matt, questo è quello che stavo cercando
Venkata SSKM Chaitanya,

@ Matt, sei sicuro che &MyStruct{ /* values */ }conti come un puntatore nullo? Il valore non è nullo.
Shuzheng,

@Matt È possibile impostare questo comportamento predefinito? Voglio omettere sempre il vuoto. (fondamentalmente non utilizzare il tag in ogni campo di tutte le strutture)
Mohit Singh

18

Come @chakrit menzionato in un commento, non è possibile ottenere questo al lavoro implementando json.Marshalersu MyStruct, e l'attuazione di una funzione di smistamento JSON personalizzata su ogni struct che l'utilizza può essere molto più lavoro. Dipende davvero dal tuo caso d'uso se vale il lavoro extra o se sei pronto a convivere con strutture vuote nel tuo JSON, ma ecco il modello che uso applicato a 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
}

Se hai strutture enormi con molti campi, questo può diventare noioso, specialmente cambiare l'implementazione di una struttura in seguito, ma a meno di riscrivere l'intero jsonpacchetto per soddisfare le tue esigenze (non è una buona idea), questo è praticamente l'unico modo in cui posso pensare di ottenere questo fatto mantenendo ancora un non-puntatore MyStructlì.

Inoltre, non è necessario utilizzare strutture inline, è possibile creare strutture con nome. Io uso LiteIDE con il completamento del codice, quindi preferisco in linea per evitare il disordine.


9

Dataè una struttura inizializzata, quindi non è considerata vuota perché encoding/jsonguarda solo il valore immediato, non i campi all'interno della struttura.

Purtroppo il ritorno nilda json.Marhslerattualmente non funziona:

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

Potresti dare anche Resultun marshaler, ma non ne vale la pena.

L'unica opzione, come suggerisce Matt, è creare Dataun puntatore e impostare il valore su nil.


1
Non vedo perché encoding/json non è possibile controllare i campi figlio della struttura. Non sarebbe molto efficiente, sì. Ma non è certamente impossibile.
nemo

@nemo vedo il tuo punto, ho cambiato la formulazione. Non lo fa perché non sarebbe efficiente. Può essere fatto json.Marshalercaso per caso.
Luca

2
È non possibile decidere indipendentemente da fatto MyStructè vuoto implementando una json.Marshalersu MyStructse stessa. Prova: play.golang.org/p/UEC8A3JGvx
chakrit

Per farlo dovresti implementarlo json.Marshalersul Resulttipo contenitore stesso, il che potrebbe essere molto scomodo.
chakrit

4

Esiste un'eccezionale proposta Golang per questa funzione che è attiva da oltre 4 anni, quindi a questo punto è lecito ritenere che non entrerà presto nella libreria standard. Come ha sottolineato @Matt, l' approccio tradizionale consiste nel convertire le strutture in puntatori a strutture . Se questo approccio non è fattibile (o non pratico), allora un'alternativa è usare un codificatore json alternativo che supporta l' omissione di strutture a valore zero .

Ho creato un mirror della libreria Golang json ( clarketm / json ) con supporto aggiunto per l' omissione di strutture a valore zero quando omitemptyviene applicato il tag. Questa libreria rileva zeroness in un modo simile al popolare encoder YAML go-YAML per il controllo in modo ricorsivo i pubblici campi struct .

per esempio

$ 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"
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.