JSON Yanıtında alanları yapıdan kaldırma veya gizleme


181

Go'da çağrıldığında bir sorgu gerçekleştiren, bir yapının örneğini oluşturan ve daha sonra arayana geri göndermeden önce bu yapıyı JSON olarak kodlayan bir API oluşturdum. Şimdi arayanın "alanlar" GET parametresinden geçirerek geri dönmek istedikleri belirli alanları seçebilmesine izin vermek istiyorum.

Bu, alan değerlerine bağlı olarak yapımın değişeceği anlamına gelir. Bir yapıdan alanları kaldırmanın herhangi bir yolu var mı? Ya da en azından bunları JSON yanıtında dinamik olarak gizlemek mi? (Not: Bazen JSON omitEmpty etiketi burada işe yaramayacak şekilde boş değerlerim var) Bunlardan hiçbiri mümkün değilse, bu daha iyi bir yol için bir öneri var mı? Şimdiden teşekkürler.

Kullandığım yapıların daha küçük bir sürümü aşağıdadır:

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

Sonra kodlamak ve böylece yanıt çıktı:

err := json.NewEncoder(c.ResponseWriter).Encode(&msg)

7
@Jacob, PuerkitoBio'nun güncellenmiş cevabına göre, soruyu yanlış okuduğunuzu düşünüyorum. (Şu anda) kabul kudretinin için "doğru cevap" olamaz senin sorunun, ancak birine burada istedi! (Şu anda) en yüksek olarak cevap cevap verebilir senin soru ama tamamen uygulanamaz buna!
Dave C

Yanıtlar:


277

EDIT: Birkaç downvotes fark ettim ve bu soru-cevap bir kez daha baktım. Çoğu insan OP'nin alanların dinamik olarak olmasını istediğini gözden kaçırıyor arayan tarafından sağlanan alan listesine göre alanların seçilmesini . Bunu statik olarak tanımlanmış json struct etiketi ile yapamazsınız.

İstediğiniz şey her zaman bir alanı json kodlamak için atlamaksa, elbette json:"-"alanı yoksaymak için kullanın (ayrıca bunun encode'a atlamaksa alanınız dışa aktarılmazsa gerekli olmadığını - bu alanlar her zaman json kodlayıcı tarafından yoksayılır). Ama bu OP'nin sorusu değil.

Cevaba yapılan yorumu alıntılamak için json:"-":

Bu [ json:"-"cevap], burada arama yapan çoğu insanın isteyeceği cevaptır, ancak sorunun cevabı değildir.


Bu durumda bir yapı yerine bir harita [string] arabirimi {} kullanırdım. Alanları çağırarak alanları kolayca kaldırabilirsiniz.deleteHaritadaki yerleşik kaldırabilirsiniz.

Yani, ilk etapta yalnızca istenen alanları sorgulayamıyorsanız.


5
büyük olasılıkla tür tanımınızı tamamen atmak istemezsiniz. Bu, bu alanlara bu alanlara erişen başka yöntemler yazmak istediğinizde olduğu gibi, satırdan rahatsız edici olacaktır. Bir ara ürün kullanmak map[string]interface{}mantıklıdır, ancak tür tanımınızı atmanızı gerektirmez.
jorelli

1
Diğer cevap bu sorunun asıl cevabıdır.
Jacob

1
Silmenin olası bir dezavantajı, bazen yapınızın (harita) birden fazla json görünümünü desteklemek isteyebileceğinizdir. Örneğin, hassas alanı olmayan istemci için json görünümü ve hassas alanı olan veritabanı için json görünümü. Neyse ki yapıyı kullanmak hala mümkündür - cevabım bir göz atın.
Adam Kurkiewicz

Sadece belirli bir ihtiyaç duyduğum için Id, ama tüm json yapısını döndürmek istemediğim için bu benim için çalışıyor . Bunun için teşekkürler!
Louie Miranda

155

json kullan: "-" `

// 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


14
OP, API'ya sorgu dizesi girişlerine dayalı olarak çıkış alanlarını dinamik olarak kontrol etmek istediklerini söylediğinden @Jacob'a katılmıyorum. Örneğin, API arayan kişi yalnızca Endüstri ve Ülke isteğinde bulunursa, geri kalanını kaldırmanız gerekir. Bu yüzden "işaretli" cevap bu soruya bir cevap olarak işaretlenmiştir. Bu yüksek oyu alan cevap, hiçbir zaman yerleşik-json-marshaler - EVER için hiçbir zaman mevcut olmayan alanları işaretlemek içindir. dinamik olarak istiyorsanız, işaretli cevap cevaptır.
eduncan911

12
Bu, burada arama yapan çoğu insanın isteyeceği cevaptır, ancak sorunun cevabı değildir.
Filip Haglund

5
Daha önce de belirtildiği gibi, OP dinamik olarak bir DTO oluşturmak için bir yöntem istiyordu.
codepushr

54

Bunu yapmanın başka bir yolu da etikete sahip bir işaretçi yapısına sahip olmaktır ,omitempty. Eğer işaretçiler boşsa , alanlar Marshalled olmaz.

Bu yöntem, haritaların ek yansımasını veya verimsiz kullanımını gerektirmez.

Bu yöntemi kullanan jorelli ile aynı örnek: http://play.golang.org/p/JJNa0m2_nw


3
+1 Tamamen katılıyorum. Bu kuralı / hileyi yerleşik marshalers ile her zaman kullanırım (ve hatta bu kurala dayalı bir CSV okuyucu / yazar da inşa ettim!) OP daha sonra * Ülke değerini sıfır olarak ayarlayamadı ve atlanacaktır. Ve güzel; y yazmış bir play.golang sağladığınız harika.
eduncan911

2
Elbette bu yöntem yansıma gerektirir, stdlib'in json-construct marshaling'i her zaman yansıma kullanır (aslında her zaman yansıma dönemi, harita veya yapı ya da her neyse kullanır).
mna

Evet, ancak diğer cevapların önerdiği arabirimleri kullanarak ek yansıma gerektirmez .
Druska

14

reflectAlan etiketlerine yansıtarak ve jsonetiket değerlerini seçerek paketi istediğiniz alanları seçmek için kullanabilirsiniz . Bu seçen bir gibi istediğiniz alanlar ve getiri yazdıkça sizin searchresults bir yöntem tanımlayın map[string]interface{}ve sonra çağrılamadı o SearchResults kendisini yapısına bir yerine. Bu yöntemi nasıl tanımlayabileceğinize ilişkin bir örnek:

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
}

ve işte bu yöntemi nasıl arayacağınızı ve seçiminizi nasıl marshal yapacağınızı gösteren çalıştırılabilir bir çözüm: http://play.golang.org/p/1K9xjQRnO8


düşünmeye gelince, selectfield desenini herhangi bir türe ve herhangi bir etiket anahtarına makul şekilde genelleştirebilirsiniz; bununla ilgili olarak SearchResult tanımına veya json anahtarına özgü hiçbir şey yoktur.
jorelli

Yansımaktan uzak kalmaya çalışıyorum ama bu tür bilgileri oldukça güzel kaydediyor ... Yapılarınızın bir validate () yönteminde if / else etiketleri bir demet daha iyi gibi görünen belgeleri belgelemek güzel (hatta eğer biri var)
Aktau

7

Az önce şerif yayınladımYapıları, yapı alanlarına ek açıklama eklenmiş etiketlere dayalı bir haritaya dönüştüren . Ardından oluşturulan haritayı mareşal (JSON veya diğerleri) yapabilirsiniz. Muhtemelen sadece arayan kişinin istediği alanların serileştirilmesine izin vermez, ancak bir grup grup kullanmanın çoğu vakayı karşılamanıza izin vereceğini hayal ediyorum. Alanlar yerine doğrudan grupların kullanılması büyük olasılıkla önbellek yeteneğini de artırır.

Misal:

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:    "alice@example.org",
        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)
}

7

Üç malzemeyi alın:

  1. reflectBir yapının tüm alanları üzerinde döngü paketi.

  2. ifİstediğiniz alanları almak için bir ifade Marshalve

  3. encoding/jsonİçin paket Marshalbeğeninize alanlarında.

Hazırlık:

  1. Onları iyi bir oranda karıştırın. Öğesinin th alanının reflect.TypeOf(your_struct).Field(i).Name()adını almak için kullanın .iyour_struct

  2. Öğesinin th alanının reflect.ValueOf(your_struct).Field(i)tür Valuetemsilini almak için kullanın .iyour_struct

  3. Kullanım fieldValue.Interface()gerçek değerini almak için (tip arayüzü {} olarak upcasted) fieldValueÇeşidi Valuearabirimi () - (dirsek kullanımına dikkat yöntem üretirinterface{}

Neyse ki süreçte herhangi bir transistör veya devre kesici yakmamayı başarırsanız, böyle bir şey almalısınız:

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
}

Porsiyon:

map[string]boolörneğin rastgele bir yapı ve dahil etmek istediğiniz alanlardan birini kullanarak

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))
    }

}

Afiyet olsun!


Uyarı! İncludeField'larınız, gerçek alanlarla eşleşmeyen alan adları içeriyorsa, geçersiz bir json alırsınız. Uyarıldın.
Adam Kurkiewicz

5

"Omitifempty" etiketleme özniteliğini kullanabilir veya isteğe bağlı alan işaretçileri oluşturabilir ve atlanmasını istediğinizleri başlatılmamış halde bırakabilirsiniz.


Bu OPs sorusu ve kullanım durumu için en doğru cevaptır.
user1943442

2
@ user1943442, değil; OP açıkça "eksiklik" in neden uygulanamaz olduğunu belirtmektedir.
Dave C

2

Ayrıca bu sorunla karşı karşıya kaldım, ilk başta http işleyicimdeki yanıtları uzmanlaşmak istedim. İlk yaklaşımım, bir yapının bilgisini başka bir yapıya kopyalayan ve daha sonra bu ikinci yapıya kopyalayan bir paket oluşturmaktı. Bu paketi yansıma kullanarak yaptım, bu yüzden bu yaklaşımı hiç sevmedim ve dinamik olarak da değildim.

Bu yüzden bunu yapmak için kodlama / json paketini değiştirmeye karar verdim. Fonksiyonlar Marshal, MarshalIndentve (Encoder) Encodeilave olarak bir alır

type F map[string]F

Mareşal için gereken alanların bir JSON'sini simüle etmek istedim, bu yüzden sadece haritadaki alanları marşalıyor.

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)
}

Henüz denemedim, ama harika görünüyor. Marshaler arayüzünün de desteklenmesi daha iyi olurdu.
huggie

1

Soru şimdi biraz eski, ama aynı konuyla bir süre önce karşılaştım ve bunu yapmanın kolay bir yolunu bulamadığım için, bu amacı yerine getiren bir kütüphane inşa ettim. Kolayca birmap[string]interface{}Statik bir yapıdan bir üretime .

https://github.com/tuvistavie/structomap


Artık tarifimdeki bir kod parçacığını kullanarak bunu kolayca yapabilirsiniz.
Adam Kurkiewicz

Parçacık, kütüphanenin bir alt kümesidir, ancak burada a'nın döndürülmesiyle ilgili önemli bir sorun []byte, çok fazla yeniden kullanılabilir olmamasıdır: örneğin, daha sonra bir alan eklemenin kolay bir yolu yoktur. Bu yüzden bir map[string]interface{}standart kitaplık JSON serileştirme parçası oluşturmak ve izin öneririz .
Daniel Perez

1

Aynı problemim yoktu ama benzerim. Aşağıdaki kod da sorununuzu çözer, elbette performans sorununa dikkat etmezseniz. Sisteminize bu tür bir çözümü uygulamadan önce, mümkünse yapınızı yeniden tasarlamanızı tavsiye ederim. Değişken yapı yanıtı göndermek aşırı mühendisliktir. Bir yanıt yapısının, bir istek ve kaynak arasındaki bir sözleşmeyi temsil ettiğine ve bağımlı isteklerin olmaması gerektiğine inanıyorum. (İstenmeyen alanları boş bırakabilirsiniz). Bazı durumlarda bu tasarımı uygulamak zorundayız, eğer bu durumda olduğunuzu düşünüyorsanız burada kullandığım play link ve kod.

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))

}

1

Bu işlevi, bazı alanları yok sayarak yapıyı JSON dizesine dönüştürmek için oluşturdum. Umarım yardımcı olur.

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
}

Örnek: https://play.golang.org/p/nmq7MFF47Gp


0

Yapımı böyle tanımladım.

type User struct {
    Username string  `json:"username" bson:"username"`
    Email    string  `json:"email" bson:"email"`
    Password *string `json:"password,omitempty" bson:"password"`
    FullName string  `json:"fullname" bson:"fullname"`
}

Ve fonksiyonumun içinde user.Password = nilMarshalllı olmamamı sağladı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.