Bagaimana cara tidak mengatur struct kosong ke JSON dengan Go?

91

Saya memiliki struct seperti ini:

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

Tetapi bahkan jika instance MyStruct benar-benar kosong (artinya, semua nilai adalah default), itu akan diserialkan sebagai:

"data":{}

Saya tahu bahwa dokumen encoding / json menentukan bahwa kolom "kosong" adalah:

false, 0, pointer nol atau nilai antarmuka, dan array, slice, peta, atau string apa pun dengan panjang nol

tetapi tanpa pertimbangan untuk struct dengan semua nilai kosong / default. Semua bidangnya juga diberi tag omitempty, tetapi ini tidak berpengaruh.

Bagaimana saya bisa mendapatkan paket JSON untuk tidak mengatur bidang saya yang merupakan struct kosong?

Matt
sumber

Jawaban:

142

Seperti yang dikatakan oleh dokumen, "sembarang penunjuk nol". - jadikan struct sebagai pointer. Pointer memiliki jelas "kosong" nilai-nilai: nil.

Fix - tentukan tipe dengan field pointer struct :

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

Kemudian nilai seperti ini:

result := Result{}

Akan marshal sebagai:

{}

Penjelasan: Perhatikan *MyStructdefinisi di tipe kami. Serialisasi JSON tidak peduli apakah itu pointer atau bukan - itu detail runtime. Jadi membuat bidang struct menjadi pointer hanya memiliki implikasi untuk kompilasi dan runtime).

Perhatikan bahwa jika Anda mengubah jenis bidang dari MyStructmenjadi *MyStruct, Anda akan memerlukan penunjuk ke nilai struct untuk mengisinya, seperti:

Data: &MyStruct{ /* values */ }
Matt
sumber
2
Bless you Matt, Ini yang saya cari
Venkata SSKM Chaitanya
@Matt, apakah Anda yakin itu &MyStruct{ /* values */ }dihitung sebagai penunjuk nol? Nilainya tidak nihil.
Shuzheng
@Matt Apakah mungkin untuk membuat perilaku default ini? Saya ingin selalu menghilangkan kekosongan. (pada dasarnya tidak menggunakan tag di setiap bidang dari semua struct)
Mohit Singh
18

Seperti @chakrit disebutkan dalam komentar, Anda tidak bisa mendapatkan ini untuk bekerja dengan menerapkan json.Marshalerpada MyStruct, dan menerapkan JSON kustom fungsi marshalling pada setiap struct yang menggunakan hal itu dapat lebih banyak bekerja. Itu benar-benar tergantung pada kasus penggunaan Anda, apakah itu sepadan dengan kerja ekstra atau apakah Anda siap untuk hidup dengan struct kosong di JSON Anda, tetapi inilah pola yang saya gunakan yang diterapkan 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
}

Jika Anda memiliki struct besar dengan banyak bidang, ini bisa menjadi membosankan, terutama mengubah implementasi struct nanti, tetapi singkatnya menulis ulang seluruh jsonpaket agar sesuai dengan kebutuhan Anda (bukan ide yang baik), ini adalah satu-satunya cara yang bisa saya pikirkan untuk mendapatkannya ini dilakukan sambil tetap menyimpan non-pointer MyStructdi sana.

Selain itu, Anda tidak harus menggunakan struct inline, Anda dapat membuat yang bernama. Saya menggunakan LiteIDE dengan penyelesaian kode, jadi saya lebih suka sebaris untuk menghindari kekacauan.

Leylandski
sumber
9

Dataadalah struct yang diinisialisasi, jadi tidak dianggap kosong karena encoding/jsonhanya melihat nilai langsung, bukan bidang di dalam struct.

Sayangnya kembali nildari json.Marhslersaat ini tidak berfungsi:

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

Anda bisa memberikan Resultmarshaler juga, tapi itu tidak sebanding dengan usahanya.

Satu-satunya pilihan, seperti yang disarankan Matt, adalah membuat Datapenunjuk dan menyetel nilainya kenil .

Luke
sumber
1
Saya tidak melihat mengapa encoding/json tidak dapat memeriksa bidang anak struct. Itu tidak akan sangat efisien, ya. Namun hal tersebut tentunya bukan tidak mungkin.
nemo
@nemo Saya mengerti maksud Anda, saya mengubah kata-katanya. Itu tidak melakukannya karena tidak efisien. Ini dapat dilakukan dengan json.Marshalerkasus per kasus.
Lukas
2
Hal ini tidak mungkin untuk memutuskan cuaca atau tidak MyStructkosong dengan menerapkan json.Marshalerpada MyStructdirinya sendiri. Bukti: play.golang.org/p/UEC8A3JGvx
chakrit
Untuk melakukan itu, Anda harus mengimplementasikan json.Marshalerpada Resulttipe yang memuat itu sendiri yang bisa sangat merepotkan.
chakrit
4

Ada proposal Golang yang luar biasa untuk fitur ini yang telah aktif selama lebih dari 4 tahun, jadi pada titik ini, dapat diasumsikan bahwa fitur ini tidak akan masuk ke perpustakaan standar dalam waktu dekat. Seperti yang ditunjukkan @Matt, pendekatan tradisionalnya adalah dengan mengubah struct menjadi pointers-to-struct . Jika pendekatan ini tidak layak (atau tidak praktis), maka alternatifnya adalah menggunakan encoder json alternatif yang tidak mendukung penghilangan struct nilai nol .

Saya membuat cermin dari pustaka Golang json ( clarketm / json ) dengan dukungan tambahan untuk menghilangkan struct nilai nol saat omitemptytag diterapkan. Library ini mendeteksi ke nol dengan cara yang mirip dengan encoder YAML go-yaml yang populer dengan memeriksa kolom struct publik secara rekursif .

misalnya

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