J'ai créé dans Go une API qui, lorsqu'elle est appelée, effectue une requête, crée une instance d'une structure, puis l'encode sous forme de code JSON avant de la renvoyer à l'appelant. J'aimerais maintenant permettre à l'appelant de pouvoir sélectionner les champs spécifiques qu'il souhaite voir renvoyés en transmettant un paramètre GET "fields".
Cela signifie que selon la valeur des champs, ma structure changerait. Est-il possible de supprimer des champs d'une structure? Ou du moins les cacher dans la réponse JSON de manière dynamique? (Remarque: j'ai parfois des valeurs vides pour que la balise JSON omitEmpty ne fonctionne pas ici). Si aucune de ces options n'est possible, existe-t-il une suggestion sur une meilleure façon de gérer cela? Merci d'avance.
Une version plus petite des structures que j'utilise est ci-dessous:
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
J'ai ensuite encodé et sorti la réponse comme suit:
err := json.NewEncoder(c.ResponseWriter).Encode(&msg)
EDIT: J'ai remarqué quelques votes négatifs et j’ai jeté un nouveau regard sur ce Q & A. La plupart des gens semblent oublier que le PO a demandé que les champs soient dynamiquement sélectionnés en fonction de la liste de champs fournie par l'appelant. Vous ne pouvez pas faire cela avec la balise json struct définie statiquement.
Si ce que vous voulez, c'est toujours ignorer un champ à coder json, utilisez bien sûr json:"-"
pour ignorer le champ (notez également que c'est pas obligatoire si votre champ n’est pas exporté - ces champs sont toujours ignorés par le codeur json). Mais ce n'est pas la question du PO.
Pour citer le commentaire sur la réponse json:"-"
:
Cette [la
json:"-"
réponse] est la réponse que voudraient la plupart des gens qui se retrouvent ici après une recherche, mais ce n'est pas la réponse à la question.
J'utiliserais une interface [string] interface {} au lieu d'une structure dans ce cas. Vous pouvez facilement supprimer des champs en appelant le delete
intégré à la carte pour supprimer les champs.
Autrement dit, si vous ne pouvez pas interroger uniquement les champs demandés.
utilisez `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"`
Une autre façon de faire est d’avoir une structure de pointeurs avec la balise ,omitempty
. Si les pointeurs sont nil , les champs ne seront pas Marshallés.
Cette méthode ne nécessitera pas de réflexion supplémentaire ou une utilisation inefficace des cartes.
Même exemple que jorelli avec cette méthode: http://play.golang.org/p/JJNa0m2_nw
Vous pouvez utiliser le package reflect
pour sélectionner les champs souhaités en réfléchissant sur les étiquettes de champ et en sélectionnant les valeurs de l'étiquette json
. Définissez une méthode sur votre type SearchResults qui sélectionne les champs souhaités et les renvoie sous forme de map[string]interface{}
, puis indiquez que à la place du paramètre SearchResults. struct lui-même. Voici un exemple de la façon dont vous pourriez définir cette méthode:
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
}
et voici une solution exécutable qui montre comment vous appelez cette méthode et commentez votre sélection: http://play.golang.org/p/1K9xjQRnO8
Je viens de publier shérif , qui transforme les structures en une carte basée sur les balises annotées sur les champs de structure. Vous pouvez ensuite marshaler (JSON ou autres) la carte générée. Cela ne vous permet probablement pas de ne sérialiser que l'ensemble des champs demandés par l'appelant, mais j'imagine que l'utilisation d'un ensemble de groupes vous permettrait de couvrir la plupart des cas. L'utilisation de groupes au lieu de champs directement augmenterait probablement la capacité de cache.
Exemple:
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)
}
Prenez trois ingrédients:
Le paquetage reflect
à boucler sur tous les champs d'une structure.
Une instruction if
pour sélectionner les champs que vous souhaitez Marshal
, et
Le package encoding/json
vers Marshal
les champs de votre choix.
Préparation:
Mélangez-les dans une bonne proportion. Utilisez reflect.TypeOf(your_struct).Field(i).Name()
pour obtenir le nom du champ i
th de your_struct
.
Utilisez reflect.ValueOf(your_struct).Field(i)
pour obtenir une représentation de type Value
d'un i
th champ de your_struct
.
Utilisez fieldValue.Interface()
pour récupérer la valeur réelle (transposée en amont dans l'interface de type {}) de la fieldValue
de type Value
(notez le crochet utilisé - l'interface () méthode = produit interface{}
Si vous réussissez heureusement à ne pas brûler de transistors ou de disjoncteurs, vous devriez obtenir quelque chose comme ceci:
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
}
Portion:
sert avec une structure arbitraire et un map[string]bool
des champs que vous voulez inclure, par exemple
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))
}
}
Bon appétit!
Vous pouvez utiliser l'attribut de marquage "omitifempty" ou créer des pointeurs de champs optionnels et laisser les champs souhaités non initialisés.
La question est maintenant un peu ancienne, mais j’ai rencontré le même problème il ya quelque temps et, comme je n’ai trouvé aucun moyen facile de le faire, j’ai construit une bibliothèque répondant à cet objectif. Cela permet de générer facilement un map[string]interface{}
à partir d'une structure statique.
Je n'ai pas eu le même problème mais similaire. Le code ci-dessous résout également votre problème, bien sûr si cela ne vous gêne pas. Avant de mettre en place ce type de solution sur votre système, je vous recommande de revoir votre structure si vous le pouvez. L'envoi d'une réponse à structure variable est une ingénierie excessive. Je crois qu'une structure de réponse représente un contrat entre une demande et une ressource et ne devrait pas être une demande de dépendance (vous pouvez rendre les champs non souhaités nuls, comme je le fais). Dans certains cas, nous devons implémenter cette conception, si vous pensez que vous êtes dans ce cas, voici le lien de lecture et le code que j'utilise.
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))
}
J'ai également fait face à ce problème, au début je voulais juste spécialiser les réponses dans mon gestionnaire http. Ma première approche consistait à créer un paquet qui copiait les informations d'une structure vers une autre structure, puis organisait cette seconde structure. J'ai fait ce paquet en utilisant la réflexion, donc, jamais aimé cette approche et je n'étais pas dynamique non plus.
J'ai donc décidé de modifier le paquet encoding/json pour ce faire. Les fonctions Marshal
, MarshalIndent
et (Encoder) Encode
reçoivent en outre un
type F map[string]F
Je voulais simuler un JSON des champs nécessaires au marshal, de sorte qu'il ne regroupe que les champs figurant sur la carte.
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)
}