Menghapus bidang dari struct atau menyembunyikannya di JSON Response

181

Saya telah membuat API in Go yang, saat dipanggil, melakukan kueri, membuat instance dari sebuah struct, dan kemudian menyandikan struct itu sebagai JSON sebelum mengirim kembali ke pemanggil. Sekarang saya ingin mengizinkan pemanggil untuk dapat memilih bidang spesifik yang ingin mereka kembalikan dengan mengirimkan parameter GET "bidang".

Ini berarti tergantung pada nilai bidang, struct saya akan berubah. Apakah ada cara untuk menghapus bidang dari struct? Atau setidaknya menyembunyikannya di respons JSON secara dinamis? (Catatan: Kadang-kadang saya memiliki nilai kosong sehingga tag omitEmpty JSON tidak akan berfungsi di sini) Jika tidak ada yang mungkin, apakah ada saran tentang cara yang lebih baik untuk menangani ini? Terima kasih sebelumnya.

Versi lebih kecil dari struct yang saya gunakan ada di bawah:

type SearchResult struct {
    Date        string      `json:"date"`
    IdCompany   int         `json:"idCompany"`
    Company     string      `json:"company"`
    IdIndustry  interface{} `json:"idIndustry"`
    Industry    string      `json:"industry"`
    IdContinent interface{} `json:"idContinent"`
    Continent   string      `json:"continent"`
    IdCountry   interface{} `json:"idCountry"`
    Country     string      `json:"country"`
    IdState     interface{} `json:"idState"`
    State       string      `json:"state"`
    IdCity      interface{} `json:"idCity"`
    City        string      `json:"city"`
} //SearchResult

type SearchResults struct {
    NumberResults int            `json:"numberResults"`
    Results       []SearchResult `json:"results"`
} //type SearchResults

Saya kemudian menyandikan dan menampilkan respons seperti ini:

err := json.NewEncoder(c.ResponseWriter).Encode(&msg)
pengguna387049
sumber
7
@ Jacob, sesuai jawaban yang diperbarui PuerkitoBio, saya pikir Anda salah membaca pertanyaan. Jawaban (saat ini) diterima mungkin bukan "jawaban yang benar" untuk pertanyaan Anda , tetapi untuk yang ditanyakan di sini! Jawaban dengan suara tertinggi (saat ini) dapat menjawab pertanyaan Anda tetapi sama sekali tidak berlaku untuk yang satu ini!
Dave C

Jawaban:

275

EDIT: Saya perhatikan beberapa downvotes dan melihat lagi Q&A ini. Sebagian besar orang sepertinya ketinggalan bahwa OP meminta bidang menjadi dinamis dipilih berdasarkan daftar bidang yang disediakan pemanggil. Anda tidak dapat melakukan ini dengan tag json struct yang ditentukan secara statis.

Jika yang Anda inginkan adalah selalu melewatkan bidang ke json-encode, maka tentu saja gunakan json:"-"untuk mengabaikan bidang tersebut (juga perhatikan bahwa ini bukan diperlukan jika bidang Anda tidak diekspor - bidang tersebut selalu diabaikan oleh penyandi json). Tapi itu bukan pertanyaan OP.

Mengutip komentar pada json:"-"jawabannya:

[ json:"-"Jawaban] ini adalah jawaban yang paling diinginkan orang-orang di sini dari pencarian, tetapi ini bukan jawaban untuk pertanyaan itu.


Saya akan menggunakan antarmuka [string] peta {} alih-alih struct dalam hal ini. Anda dapat dengan mudah menghapus bidang dengan memanggildelete built-in pada peta untuk menghapus bidang.

Yaitu, jika Anda tidak dapat meminta hanya untuk bidang yang diminta di tempat pertama.

mna
sumber
4
Anda kemungkinan besar tidak ingin membuang definisi tipe Anda sepenuhnya. Itu akan menyusahkan di telepon, seperti ketika Anda ingin menulis metode lain pada jenis ini yang mengakses bidang tersebut. Menggunakan perantara map[string]interface{}memang masuk akal, tetapi itu tidak mengharuskan Anda membuang definisi tipe Anda.
jorelli
1
Jawaban lainnya adalah jawaban aktual untuk pertanyaan ini.
Yakub
1
Kemungkinan kelemahan dari penghapusan adalah bahwa Anda kadang-kadang mungkin ingin mendukung beberapa tampilan json pada struct Anda (peta). Misalnya tampilan json untuk klien tanpa bidang sensitif, dan tampilan json untuk database DENGAN bidang sensitif. Untungnya masih mungkin untuk menggunakan struct - lihat saja jawaban saya.
Adam Kurkiewicz 8-15
Ini bekerja untuk saya karena saya hanya perlu spesifik Idtetapi, tidak ingin mengembalikan seluruh json struct. Terima kasih untuk ini!
Louie Miranda
155

gunakan `json:" - "`

// Field is ignored by this package.
Field int `json:"-"`

// Field appears in JSON as key "myName".
Field int `json:"myName"`

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

doc: http://golang.org/pkg/encoding/json/#Marshal

Diberikan jazz
sumber
14
Saya tidak setuju @ Jacob karena OP mengatakan mereka ingin secara dinamis mengontrol bidang output berdasarkan entri string kueri ke API. Misalnya, jika penelepon ke API hanya meminta Industri dan Negara, Anda harus menghapus sisanya. Inilah mengapa jawaban "ticked" ditandai sebagai jawaban untuk pertanyaan ini. Jawaban yang sangat banyak dipilih ini adalah untuk menandai bidang yang secara eksplisit tidak pernah tersedia untuk json-marshaler - EVER. jika Anda menginginkannya secara dinamis, jawaban yang dicentang adalah jawabannya.
eduncan911
11
Ini adalah jawaban yang diinginkan kebanyakan orang untuk mencari di sini, tetapi itu bukan jawaban untuk pertanyaan itu.
Filip Haglund
5
Seperti yang telah dinyatakan, OP meminta metode untuk secara dinamis membentuk DTO.
codepushr
53

Cara lain untuk melakukan ini adalah memiliki struct pointer dengan ,omitemptytag. Jika petunjuknya nol , bidang tidak akan Marshall.

Metode ini tidak memerlukan refleksi tambahan atau penggunaan peta yang tidak efisien.

Contoh yang sama seperti jorelli menggunakan metode ini: http://play.golang.org/p/JJNa0m2_nw

Druska
sumber
3
+1 Sepenuhnya setuju. Saya menggunakan aturan / trik ini sepanjang waktu dengan built-in marshaler (dan bahkan membangun pembaca / penulis CSV berdasarkan dari aturan ini juga! - Saya dapat membuka-sumber yang segera setelah paket csv go lainnya). OP kemudian tidak bisa mengatur nilai Negara * menjadi nol, dan itu akan dihilangkan. Dan luar biasa bahwa Anda memberikan play.golang y bagus diketik juga.
eduncan911
2
Tentu saja metode itu membutuhkan refleksi, marshaling json-to-struct stdlib selalu menggunakan refleksi (sebenarnya selalu menggunakan periode refleksi, peta atau struct atau apa pun).
mna
Ya, tapi itu tidak memerlukan refleksi tambahan menggunakan antarmuka, yang direkomendasikan oleh beberapa jawaban lain.
Druska
14

Anda dapat menggunakan reflectpaket untuk memilih bidang yang Anda inginkan dengan merenungkan tag bidang dan memilih nilai jsontag. Tentukan metode pada tipe SearchResults Anda yang memilih bidang yang Anda inginkan dan mengembalikannya sebagai a map[string]interface{}, dan lalu buat itu sebagai ganti struct SearchResults itu sendiri. Berikut ini contoh bagaimana Anda dapat mendefinisikan metode itu:

func fieldSet(fields ...string) map[string]bool {
    set := make(map[string]bool, len(fields))
    for _, s := range fields {
        set[s] = true
    }
    return set
}

func (s *SearchResult) SelectFields(fields ...string) map[string]interface{} {
    fs := fieldSet(fields...)
    rt, rv := reflect.TypeOf(*s), reflect.ValueOf(*s)
    out := make(map[string]interface{}, rt.NumField())
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        jsonKey := field.Tag.Get("json")
        if fs[jsonKey] {
            out[jsonKey] = rv.Field(i).Interface()
        }
    }
    return out
}

dan inilah solusi runnable yang menunjukkan bagaimana Anda akan memanggil metode ini dan menyusun pilihan Anda: http://play.golang.org/p/1K9xjQRnO8

jorelli
sumber
kalau dipikir-pikir, Anda bisa menggeneralisasi secara wajar pola bidang pilih untuk jenis apa pun dan kunci tag apa pun; tidak ada tentang ini yang khusus untuk definisi SearchResult atau kunci json.
jorelli
Saya mencoba untuk menghindari refleksi tetapi ini menyimpan informasi jenis dengan cukup baik ... Senang memiliki kode yang mendokumentasikan struktur Anda lebih baik daripada sekelompok tag if / else dalam metode validate () (jika Anda bahkan have one)
Aktau
7

Saya baru saja menerbitkan sheriff , yang mengubah struct ke peta berdasarkan tag yang dijelaskan pada bidang struct. Anda kemudian dapat menyusun (JSON atau yang lain) peta yang dihasilkan. Mungkin tidak memungkinkan Anda hanya membuat serial set bidang yang diminta oleh penelepon, tetapi saya membayangkan menggunakan serangkaian grup akan memungkinkan Anda untuk menutupi sebagian besar kasus. Menggunakan grup alih-alih bidang secara langsung kemungkinan besar juga akan meningkatkan kemampuan cache.

Contoh:

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/hashicorp/go-version"
    "github.com/liip/sheriff"
)

type User struct {
    Username string   `json:"username" groups:"api"`
    Email    string   `json:"email" groups:"personal"`
    Name     string   `json:"name" groups:"api"`
    Roles    []string `json:"roles" groups:"api" since:"2"`
}

func main() {
    user := User{
        Username: "alice",
        Email:    "[email protected]",
        Name:     "Alice",
        Roles:    []string{"user", "admin"},
    }

    v2, err := version.NewVersion("2.0.0")
    if err != nil {
        log.Panic(err)
    }

    o := &sheriff.Options{
        Groups:     []string{"api"},
        ApiVersion: v2,
    }

    data, err := sheriff.Marshal(o, user)
    if err != nil {
        log.Panic(err)
    }

    output, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        log.Panic(err)
    }
    fmt.Printf("%s", output)
}
Michael Weibel
sumber
7

Ambil tiga bahan:

  1. The reflectpaket loop atas semua bidang struct.

  2. Sebuah ifpernyataan untuk mengambil bidang yang ingin Anda Marshal, dan

  3. The encoding/jsonpaket untuk Marshalbidang sesuai dengan keinginan Anda.

Persiapan:

  1. Blender mereka dalam proporsi yang baik. Gunakan reflect.TypeOf(your_struct).Field(i).Name()untuk mendapatkan nama ibidang ke your_struct.

  2. Gunakan reflect.ValueOf(your_struct).Field(i)untuk mendapatkan Valuerepresentasi tipe ibidang ke-5 your_struct.

  3. Gunakan fieldValue.Interface()untuk mengambil nilai yang sebenarnya (upcasted untuk jenis antarmuka {}) dari fieldValuejenis Value(perhatikan braket penggunaan - Interface () metode menghasilkaninterface{}

Jika Anda beruntung tidak membakar transistor atau pemutus sirkuit apa pun dalam proses, Anda harus mendapatkan sesuatu seperti ini:

func MarshalOnlyFields(structa interface{},
    includeFields map[string]bool) (jsona []byte, status error) {
    value := reflect.ValueOf(structa)
    typa := reflect.TypeOf(structa)
    size := value.NumField()
    jsona = append(jsona, '{')
    for i := 0; i < size; i++ {
        structValue := value.Field(i)
        var fieldName string = typa.Field(i).Name
        if marshalledField, marshalStatus := json.Marshal((structValue).Interface()); marshalStatus != nil {
            return []byte{}, marshalStatus
        } else {
            if includeFields[fieldName] {
                jsona = append(jsona, '"')
                jsona = append(jsona, []byte(fieldName)...)
                jsona = append(jsona, '"')
                jsona = append(jsona, ':')
                jsona = append(jsona, (marshalledField)...)
                if i+1 != len(includeFields) {
                    jsona = append(jsona, ',')
                }
            }
        }
    }
    jsona = append(jsona, '}')
    return
}

Porsi:

melayani dengan struct sewenang-wenang dan map[string]boolbidang yang ingin Anda sertakan, misalnya

type magic struct {
    Magic1 int
    Magic2 string
    Magic3 [2]int
}

func main() {
    var magic = magic{0, "tusia", [2]int{0, 1}}
    if json, status := MarshalOnlyFields(magic, map[string]bool{"Magic1": true}); status != nil {
        println("error")
    } else {
        fmt.Println(string(json))
    }

}

Selamat makan!

Adam Kurkiewicz
sumber
Peringatan! Jika includeFields Anda berisi nama bidang, yang tidak cocok dengan bidang sebenarnya, Anda akan mendapatkan json yang tidak valid. Anda telah diperingatkan.
Adam Kurkiewicz 8-15
5

Anda dapat menggunakan atribut penandaan "omitifempty" atau membuat pointer bidang opsional dan membiarkan yang ingin Anda lewati diinisialisasi.

deemok
sumber
Ini adalah jawaban yang paling benar untuk pertanyaan OP dan kasus penggunaan.
user1943442
2
@ user1943442, bukan bukan; OP secara eksplisit menyebutkan mengapa "omitempty" tidak dapat diterapkan.
Dave C
2

Saya juga menghadapi masalah ini, pada awalnya saya hanya ingin mengkhususkan respon di handler http saya. Pendekatan pertama saya adalah membuat paket yang menyalin informasi struct ke struct lain dan kemudian menyusun struct kedua itu. Saya melakukan paket itu menggunakan refleksi, jadi, tidak pernah menyukai pendekatan itu dan juga saya tidak dinamis.

Jadi saya memutuskan untuk memodifikasi paket encoding / json untuk melakukan ini. Fungsi Marshal, MarshalIndentdan (Encoder) Encodetambahan menerima a

type F map[string]F

Saya ingin mensimulasikan JSON dari bidang yang diperlukan untuk marshal, sehingga hanya marsekal bidang yang ada di peta.

https://github.com/JuanTorr/jsont

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/JuanTorr/jsont"
)

type SearchResult struct {
    Date        string      `json:"date"`
    IdCompany   int         `json:"idCompany"`
    Company     string      `json:"company"`
    IdIndustry  interface{} `json:"idIndustry"`
    Industry    string      `json:"industry"`
    IdContinent interface{} `json:"idContinent"`
    Continent   string      `json:"continent"`
    IdCountry   interface{} `json:"idCountry"`
    Country     string      `json:"country"`
    IdState     interface{} `json:"idState"`
    State       string      `json:"state"`
    IdCity      interface{} `json:"idCity"`
    City        string      `json:"city"`
} //SearchResult

type SearchResults struct {
    NumberResults int            `json:"numberResults"`
    Results       []SearchResult `json:"results"`
} //type SearchResults
func main() {
    msg := SearchResults{
        NumberResults: 2,
        Results: []SearchResult{
            {
                Date:        "12-12-12",
                IdCompany:   1,
                Company:     "alfa",
                IdIndustry:  1,
                Industry:    "IT",
                IdContinent: 1,
                Continent:   "america",
                IdCountry:   1,
                Country:     "México",
                IdState:     1,
                State:       "CDMX",
                IdCity:      1,
                City:        "Atz",
            },
            {
                Date:        "12-12-12",
                IdCompany:   2,
                Company:     "beta",
                IdIndustry:  1,
                Industry:    "IT",
                IdContinent: 1,
                Continent:   "america",
                IdCountry:   2,
                Country:     "USA",
                IdState:     2,
                State:       "TX",
                IdCity:      2,
                City:        "XYZ",
            },
        },
    }
    fmt.Println(msg)
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        //{"numberResults":2,"results":[{"date":"12-12-12","idCompany":1,"idIndustry":1,"country":"México"},{"date":"12-12-12","idCompany":2,"idIndustry":1,"country":"USA"}]}
        err := jsont.NewEncoder(w).Encode(msg, jsont.F{
            "numberResults": nil,
            "results": jsont.F{
                "date":       nil,
                "idCompany":  nil,
                "idIndustry": nil,
                "country":    nil,
            },
        })
        if err != nil {
            log.Fatal(err)
        }
    })

    http.ListenAndServe(":3009", nil)
}
Juan Torres
sumber
Saya belum mencobanya, tetapi ini terlihat hebat. Akan lebih baik lagi jika antarmuka Marshaler juga didukung.
huggie
1

Pertanyaannya sekarang sudah agak lama, tetapi saya menemukan masalah yang sama beberapa waktu yang lalu, dan karena saya tidak menemukan cara mudah untuk melakukan ini, saya membangun perpustakaan yang memenuhi tujuan ini. Hal ini memungkinkan untuk dengan mudah menghasilkan map[string]interface{}dari struct statis.

https://github.com/tuvistavie/structomap

Daniel Perez
sumber
Sekarang Anda dapat melakukannya dengan mudah menggunakan cuplikan kode dari resep saya.
Adam Kurkiewicz 8-15
Cuplikan adalah subset dari pustaka, tetapi masalah utama di sini tentang mengembalikan a []byteadalah bahwa itu tidak terlalu dapat digunakan kembali: tidak ada cara mudah untuk menambahkan bidang sesudahnya, misalnya. Jadi saya akan menyarankan untuk membuat map[string]interface{}dan membiarkan bagian serialisasi JSON ke perpustakaan standar.
Daniel Perez
1

Saya tidak memiliki masalah yang sama tetapi serupa. Kode di bawah ini memecahkan masalah Anda juga, tentu saja jika Anda tidak keberatan dengan masalah kinerja. Sebelum mengimplementasikan solusi semacam itu ke sistem Anda, saya sarankan Anda untuk mendesain ulang struktur Anda jika Anda bisa. Mengirim tanggapan struktur variabel adalah rekayasa berlebihan. Saya percaya struktur respons mewakili kontrak antara permintaan dan sumber daya dan tidak boleh tergantung pada permintaan. (Anda dapat membuat bidang yang tidak diinginkan menjadi nol, saya lakukan). Dalam beberapa kasus, kami harus menerapkan desain ini, jika Anda yakin Anda dalam hal ini, inilah tautan main dan kode yang saya gunakan.

type User2 struct {
    ID       int    `groups:"id" json:"id,omitempty"`
    Username string `groups:"username" json:"username,omitempty"`
    Nickname string `groups:"nickname" json:"nickname,omitempty"`
}

type User struct {
    ID       int    `groups:"private,public" json:"id,omitempty"`
    Username string `groups:"private" json:"username,omitempty"`
    Nickname string `groups:"public" json:"nickname,omitempty"`
}

var (
    tagName = "groups"
)

//OmitFields sets fields nil by checking their tag group value and access control tags(acTags)
func OmitFields(obj interface{}, acTags []string) {
    //nilV := reflect.Value{}
    sv := reflect.ValueOf(obj).Elem()
    st := sv.Type()
    if sv.Kind() == reflect.Struct {
        for i := 0; i < st.NumField(); i++ {
            fieldVal := sv.Field(i)
            if fieldVal.CanSet() {
                tagStr := st.Field(i).Tag.Get(tagName)
                if len(tagStr) == 0 {
                    continue
                }
                tagList := strings.Split(strings.Replace(tagStr, " ", "", -1), ",")
                //fmt.Println(tagList)
                // ContainsCommonItem checks whether there is at least one common item in arrays
                if !ContainsCommonItem(tagList, acTags) {
                    fieldVal.Set(reflect.Zero(fieldVal.Type()))
                }
            }
        }
    }
}

//ContainsCommonItem checks if arrays have at least one equal item
func ContainsCommonItem(arr1 []string, arr2 []string) bool {
    for i := 0; i < len(arr1); i++ {
        for j := 0; j < len(arr2); j++ {
            if arr1[i] == arr2[j] {
                return true
            }
        }
    }
    return false
}
func main() {
    u := User{ID: 1, Username: "very secret", Nickname: "hinzir"}
    //assume authenticated user doesn't has permission to access private fields
    OmitFields(&u, []string{"public"}) 
    bytes, _ := json.Marshal(&u)
    fmt.Println(string(bytes))


    u2 := User2{ID: 1, Username: "very secret", Nickname: "hinzir"}
    //you want to filter fields by field names
    OmitFields(&u2, []string{"id", "nickname"}) 
    bytes, _ = json.Marshal(&u2)
    fmt.Println(string(bytes))

}
RockOnGom
sumber
1

Saya membuat fungsi ini untuk mengkonversi struct ke string JSON dengan mengabaikan beberapa bidang. Semoga ini bisa membantu.

func GetJSONString(obj interface{}, ignoreFields ...string) (string, error) {
    toJson, err := json.Marshal(obj)
    if err != nil {
        return "", err
    }

    if len(ignoreFields) == 0 {
        return string(toJson), nil
    }

    toMap := map[string]interface{}{}
    json.Unmarshal([]byte(string(toJson)), &toMap)

    for _, field := range ignoreFields {
        delete(toMap, field)
    }

    toJson, err = json.Marshal(toMap)
    if err != nil {
        return "", err
    }
    return string(toJson), nil
}

Contoh: https://play.golang.org/p/nmq7MFF47Gp

Chhaileng
sumber