253 votes

Supprimer des champs de la structure ou les cacher dans la réponse JSON

J'ai créé une API en Go qui, lorsqu'elle est appelée, effectue une requête, crée une instance d'une structure, puis encode cette structure en JSON avant de la renvoyer à l'appelant. J'aimerais maintenant permettre à l'appelant de sélectionner les champs spécifiques qu'il souhaite obtenir en passant un paramètre GET "fields".

Cela signifie que, selon la ou les valeurs des champs, ma structure change. Existe-t-il un moyen de supprimer les champs d'une structure ? Ou au moins de les masquer dynamiquement dans la réponse JSON (Remarque : j'ai parfois des valeurs vides, donc la balise JSON omitEmpty ne fonctionnera pas ici). Si aucune de ces possibilités n'est envisageable, avez-vous une suggestion sur une meilleure façon de gérer cela ?

Une version plus petite des structs 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

Je code ensuite la réponse et l'envoie comme suit :

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

394voto

PuerkitoBio Points 4721

La question demande que les champs soient dynamiquement sélectionnés sur la base de la liste de champs fournie par l'appelant. Cela n'est pas possible avec la balise json struct définie de manière statique.

Si ce que vous voulez, c'est toujours sauter un champ à coder en json, puis bien sûr utiliser json:"-" pour ignorer le champ. (Notez également que c'est no requis si votre champ est non exporté ; ces champs sont toujours ignorés par l'encodeur json). Ce n'est pas ce que la question demande.

Pour citer le commentaire sur le json:"-" réponse :

Ceci [le json:"-" réponse] est la réponse que la plupart des personnes qui se retrouvent ici après avoir effectué des recherches souhaitent obtenir, mais ce n'est pas la réponse à la question.

J'utiliserais un map[string]interface{} au lieu d'une structure dans ce cas. Vous pouvez facilement supprimer des champs en appelant la fonction delete intégré sur la carte pour les champs à supprimer.

C'est-à-dire, si vous ne pouvez pas interroger uniquement les champs demandés en premier lieu.

199voto

GivenJazz Points 2013

Utiliser `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

70voto

Druska Points 828

Une autre façon de faire est d'avoir une structure de pointeurs con el ,omitempty étiquette. Si les pointeurs sont néant les champs ne seront pas marqués.

Cette méthode ne nécessitera pas de réflexion supplémentaire ni d'utilisation inefficace des cartes.

Même exemple que jorelli en utilisant cette méthode : http://play.golang.org/p/JJNa0m2_nw

18voto

jorelli Points 2494

Vous pouvez utiliser le reflect pour sélectionner les champs que vous voulez en réfléchissant sur les balises des champs et en sélectionnant le bouton json les valeurs des balises. Définissez une méthode sur votre type SearchResults qui sélectionne les champs souhaités et les renvoie sous la forme d'une balise map[string]interface{} et ensuite maréchal que au lieu de la structure SearchResults elle-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 appeler cette méthode et mettre en commun votre sélection : http://play.golang.org/p/1K9xjQRnO8

9voto

Michael Weibel Points 615

Je viens de publier shérif qui transforme les structures en une carte sur la base des balises annotées sur les champs de la structure. Vous pouvez ensuite marshaler (JSON ou autres) la carte générée. Il ne vous permet probablement pas de sérialiser uniquement 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 des champs directement augmenterait très probablement aussi la possibilité de mise en 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:    "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)
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X