5
votes

Masquer les propriétés d'un JSON

J'ai une table struct avec 2 joueurs, mais je dois ignorer certaines propriétés de la structure Player lorsque j'envoie JSON.

Je pourrais utiliser json: "-" , mais la propriété sera TOUJOURS ignorée, et je ne dois l'ignorer que lorsque j'envoie la structure Table. J'ai besoin de ces propriétés lorsque j'envoie le Player dans d'autres parties du code.

J'ai:

type PlayerAlias struct {
    Id            Int64   `json:"id"`
    Username      string  `json:"username,omitempty"`
    Avatar        string  `json:avatar,omitempty"`
}

J'ai besoin :

myTable = new(Table)

myTable.PlayerBottom.Email = ""
myTable.PlayerBottom.Birthdate = ""
myTable.PlayerTop.Email = ""
myTable.PlayerTop.Birthdate = ""

Les joueurs proviennent de la base de données, donc les propriétés ne sont pas vides.

a) Je pourrais faire quelque chose comme:

{
    "Table": {
        "id": 1,
        "playerBottom": {
            "id": 1,
            "username": "peter",
            "avatar": "avatar.png"
        },
        "playerTop": {
            "id": 1,
            "username": "peter",
            "avatar": "avatar.png"
        }

    }
}

donc ces propriétés seront ignorées dans le JSON, grâce à json: "omitempty" , mais c'est une mauvaise idée.

b) Je pourrais utiliser quelque chose comme une structure d'alias mais Table m'attend à ce que PlayerBottom soit de type Player et non PlayerAlias ​​ , mais je ne sais pas comment l'implémenter:

type Player struct {
    Id            Int64   `json:"id"`
    Username      string  `json:"username,omitempty"`
    Password      string          `json:"-,omitempty"`
    Email         string          `json:"email,omitempty"`
    Birthdate     time.Time       `json:"birthdate,omitempty"`
    Avatar        string  `json:avatar,omitempty"`
}

type Table struct {
    Id           int       `json:"id"`
    PlayerTop    Player      `json:"playerTop"`
    PlayerBottom Player      `json:"playerBottom"`
}

c) J'ai essayé d'ajouter dynamiquement json: "-" aux propriétés qui Je ne veux pas du JSON avant de l'envoyer, mais c'était un gâchis.


1 commentaires

Juste un rappel: ajouter json: "foo, omitempty" sur une chaîne vide ne supprimera pas complètement le champ, et une chaîne vide est toujours une valeur. Vous auriez besoin d'utiliser des pointeurs et de les définir sur zéro


5 Réponses :


3
votes

Vous pouvez créer un Marshaler personnalisé pour les types Table . Voici l'interface que vous devez implémenter:

https://golang.org/pkg / encoding / json / # Marshaler

type Account struct {
    Id   int32
    Name string
}

func (a Account) MarshalJSON() ([]byte, error) {
    m := map[string]string{
        "id":   fmt.Sprintf("0x%08x", a.Id),
        "name": a.Name,
    }
    return json.Marshal(m)
}

func main() {
    joe := Account{Id: 123, Name: "Joe"}
    fmt.Println(joe)

    s, _ := json.Marshal(joe)
    fmt.Println(string(s))
}

Ensuite, vous supprimeriez la balise - du Player (car lorsque vous le marshalez ailleurs, vous devez conserver les champs) et ne l'ignorez que dans la méthode personnalisée MarshalJSON de Table .


Voici un exemple simple (non lié) d'implémentation d'un marshaling personnalisé pour un type, encodant l'un des champs en hexadécimal:

type Marshaler interface {
        MarshalJSON() ([]byte, error)
}

Comme vous pouvez le voir ici, un tel marshaling est facile à faire en construisant une carte avec juste les champs dont vous avez besoin et en la passant à json.Marshal . Pour votre Table et Player , cela ne donnera que quelques lignes de code trivial. À mon humble avis, il vaut mieux faire cela que de modifier les types et de les compliquer avec des incorporations / alias, juste pour le bien de l'encodage JSON.


1 commentaires

Si vous avez besoin d'un type spécifique pour marshal / unmarshal d'une manière spécifique, alors je suis d'accord: utilisez des marshallers personnalisés. Cependant, j'ai l'impression que l'OP veut que les types soient rassemblés différemment selon ils sont utilisés, donc vous avez besoin d'un marshaller personnalisé au niveau de Table , ce qui compliquent davantage les choses dans mon expérience.



0
votes

Il existe plusieurs façons d'y parvenir. La première serait de créer un marshaller personnalisé pour le type Table . Ceci est cependant quelque peu fastidieux et peut être assez restrictif. Il existe, à mon humble avis, un moyen plus simple de faire la même chose: les types d'intégration:

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

type marshalTable {
    Table
    // assuming the PartialPlayer type above
    PlayerTop    PartialPlayer `json:"playerTop"`
    PlayerBottom PartialPlayer `json:"playerBottom"`
}

func (t Table) MarshalJSON() ([]byte, error) {
    mt := marshalTable{
        Table:        t,
        PlayerTop:    PartialPlayer{
            Player: t.PlayerTop,
        },
        PlayerBottom: PartialPlayer{
            Player: t.PlayerBottom,
        },
    }
    return json.Marshal(mt)
}

Vous pouvez désormais accéder à toutes les données que vous souhaitez, et vous pouvez même ajouter des getters pour l'accès indirect aux données:

tbl := Table{
    Id: 213,
    PlayerTop: PartialPlayer{
        Player: playerVar,
    },
    PlayerBottom: PartialPlayer{
        Player: player2Var,
    },
}

Notez que vous n'avez pas besoin d'utiliser ces fonctions getter. Le champ Id n'est pas écrasé, donc si j'ai une variable PartialPlayer , je peux accéder directement à la valeur:

type Table struct {
    Id           int            `json:"id"`
    PlayerTop    PartialPlayer  `json:"playerTop"`
    PlayerBottom PartialPlayer  `json:"playerBottom"`
}

Vous pouvez accéder aux champs surchargés / masqués en spécifiant que vous voulez que la valeur soit conservée sur le type incorporé, sans fonction également:

fmt.Printf("Email on partial: '%s', but I can see '%s'\n", pp.Email, pp.Player.Email)

Ce dernier imprimera Email on partial: '', mais je peux voir 'foo@bar.com' .

Utilisez ce type dans Table comme ceci:

pp := PartialPlayer{
    Player: playerVar,
}
fmt.Printf("Player ID: %v\n", pp.Id) // still works

Initialiser:

func (pp PartialPlayer) GetEmail() string {
    if pp.Email == "" {
        return pp.Player.Email // get embedded Email value
    }
    return pp.Email // add override value
}

Cela fonctionne très bien. L'avantage de cette approche est que le marshalling vers et depuis JSON ne nécessite pas d'appeler vos fonctions de marshaller personnalisées, ni de créer / mapper des types intermédiaires comme des cartes ou des types cachés, etc.

Si vous le souhaitez hade un autre champ, ajoutez-le simplement au type PartialPlayer . Si vous souhaitez afficher un champ comme Email , supprimez-le simplement du type PartialPlayer , travail terminé.


Maintenant, pour une approche avec un marshaller personnalisé:

type PartialPlayer struct {
     Player // embed the entire type
     Email string `json:"-"` // override fields and add the tag to exclude them
     Birthdate string `json:"-"`
}

Ce n'est pas trop différent de créer une interface de type map [string] {} ici, mais en utilisant l'incorporation de type, vous pas besoin de mettre à jour la fonction marshaller chaque fois qu'un champ est renommé ou modifié sur le type Player .

En utilisant cette approche, votre Table type peut être utilisé exactement de la même manière que vous le faites actuellement, mais la sortie JSON n'inclura pas les champs Email et Birthdate .

p >


3 commentaires

mais alors j'aurais besoin d'une fonction pour obtenir chaque propriété de Player, donc si elle a 20 propriétés, j'aurais besoin de 20 fonctions comme func (pp PartialPlayer) GetXXXXX () string {} non?


Vous n'avez pas besoin de ces fonctions, elles sont pratiques. Vous pouvez accéder directement aux champs incorporés, sauf s'ils sont "remplacés", auquel cas vous utilisez soit v.Player.Email , mais le getter fait abstraction de cela. C'est complètement facultatif cependant. J'ai mis à jour ma réponse pour clarifier un peu ça


@gnutella: a également mis à jour la réponse pour utiliser un marshaller personnalisé, toujours en utilisant des types incorporés, mais avec une brève explication pourquoi je maintiens l'incorporation est la meilleure approche ici, par opposition à la création manuelle d'une map [string] interface {} < / code>



0
votes

Les types qui ne diffèrent que par leurs balises de champ sont convertibles entre eux depuis Go 1.8 . Vous pouvez donc définir un ou plusieurs types de "vue" pour les joueurs et en choisir un qui correspond à votre cas d'utilisation lors du marshaling.

L'avantage par rapport à l'intégration ou à l'implémentation de json.Marshaler est que chaque fois que vous ajoutez un nouveau champ à Player , le compilateur vous oblige à mettre à jour également chaque type de vue, c'est-à-dire vous devez prendre une décision consciente d'inclure ou non le nouveau champ dans chaque vue.

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Player struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-,omitempty"`
    Email     string    `json:"email,omitempty"`
    Birthdate time.Time `json:"birthdate,omitempty"`
    Avatar    string    `json:"avatar,omitempty"`
}

// PlayerSummary has the same underlying type as Player, but omits some fields 
// in the JSON representation.
type PlayerSummary struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-"`
    Email     string    `json:"-"`
    Birthdate time.Time `json:"-"`
    Avatar    string    `json:"avatar,omitempty"`
}

type Table struct {
    Id           int           `json:"id"`
    PlayerTop    PlayerSummary `json:"playerTop"`
    PlayerBottom PlayerSummary `json:"playerBottom"`
}

func main() {
    p1 := Player{
        Id:        1,
        Username:  "Alice",
        Email:     "alice@example.com",
        Birthdate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
        Avatar:    "https://www.gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060",
    }
    p2 := Player{
        Id:        2,
        Username:  "Bob",
        Email:     "bob@example.com",
        Birthdate: time.Date(1998, 6, 1, 0, 0, 0, 0, time.UTC),
        Avatar:    "https://www.gravatar.com/avatar/4b9bb80620f03eb3719e0a061c14283d",
    }

    b, _ := json.MarshalIndent(Table{
        Id:           0,
        PlayerTop:    PlayerSummary(p1), // marshal p1 as PlayerSummary
        PlayerBottom: PlayerSummary(p2), // marshal p2 as PlayerSummary
    }, "", "  ")

    fmt.Println(string(b))
}

// Output:
// {
//   "id": 0,
//   "playerTop": {
//     "id": 1,
//     "username": "Alice",
//     "avatar": "https://www.gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060"
//   },
//   "playerBottom": {
//     "id": 2,
//     "username": "Bob",
//     "avatar": "https://www.gravatar.com/avatar/4b9bb80620f03eb3719e0a061c14283d"
//   }
// }

Essayez-le sur le Playground: https://play.golang.org/p/a9V2uvOJX3Y


À part: pensez à supprimer le champ Mot de passe du joueur. Le mot de passe (hachage) n'est généralement utilisé que par très peu de fonctions. Les fonctions qui en ont besoin peuvent accepter le lecteur et le mot de passe comme arguments séparés. De cette façon, vous éliminez le risque de fuite accidentelle du mot de passe (dans les messages de journal, par exemple).


0 commentaires

1
votes

Le marshaller personnalisé est un excellent moyen de changer la façon dont votre objet est mappé vers JSON. Dans votre cas cependant, je ne suggérerais pas cela, au cas où vous auriez besoin de mapper votre objet entier sur JSON à un autre moment (par exemple pour un outil d'administration).

Quelques points clés de cette réponse:

  • Toutes les valeurs sont exposées en interne
  • À partir du code marshall, il est clair que les valeurs seront exclues et il est facile d'accéder au code qui exclut les valeurs
  • Réduisez les répétitions et les nouveaux types
  • Je suggérerais simplement de définir une fonction sur votre structure le retourne une carte des champs que vous souhaitez exposer.

    De votre exemple: p >

    func main() {
        p1 := Player{Id: 1, Username: "peter", Avatar: "avatar.png", Email: "PRIVATE"}
        p2 := Player{Id: 1, Username: "peter", Avatar: "avatar.png", Email: "PRIVATE"}
        t := Table{Id: 1, PlayerTop: p1, PlayerBottom: p2}
        admin, _ :=  json.Marshal(t)
        public, _ := json.Marshal(t.PublicInfo())
        fmt.Println(fmt.Sprintf("For admins: %s", string(admin)))
        fmt.Println(fmt.Sprintf("For public: %s", string(public)))
    }
    /*
    Output: 
    For admins: {"id":1,"playerTop":{"id":1,"username":"peter","email":"PRIVATE","birthdate":"0001-01-01T00:00:00Z","avatar":"avatar.png"},"playerBottom":{"id":1,"username":"peter","email":"PRIVATE","birthdate":"0001-01-01T00:00:00Z","avatar":"avatar.png"}}
    For public: {"id":1,"playerBottom":{"avatar":"avatar.png","id":1,"username":"peter"},"playerTop":{"avatar":"avatar.png","id":1,"username":"peter"}}
    */
    

    Il y a plusieurs façons de faire des bulles dans l'utilisation de cette fonction. Un moyen simple consiste à faire en sorte que la structure Table utilise des cartes pour PlayerTop et PlayerBottom:

    type Table struct {
        Id           int    `json:"id"`
        PlayerTop    Player `json:"playerTop"`
        PlayerBottom Player `json:"playerBottom"`
    }
    
    func (t Table) PublicInfo() map[string]interface{} {
        return map[string]interface{}{
            "id":           t.Id,
            "playerTop":    t.PlayerTop.PublicInfo(),
            "playerBottom": t.PlayerBottom.PublicInfo(),
        }
    }
    

    Le regroupement en JSON renverra les champs souhaités. Et vous n'avez besoin de modifier qu'un seul endroit pour ajouter / supprimer des champs du JSON.

    Si vous utilisez le type Table en interne et avez besoin d'accéder aux lecteurs à partir de celui-ci, alors vous peut encore avoir besoin de stocker la structure complète du Player sur la Table . Je suivrais simplement le modèle Public ci-dessus avec une table comme ceci:

    type Table struct {
        Id           int                         `json:"id"`
        PlayerTop    map[string]interface{}      `json:"playerTop"`
        PlayerBottom map[string]interface{}      `json:"playerBottom"`
    }
    
    func NewTable(id int, playerTop, playerBottom Player) Table {
        return Table{Id: id, 
                     PlayerTop: playerTop.PublicInfo(), 
                     PlayerBottom: playerBottom.PublicInfo()}
    }
    

    Maintenant, lorsque vous créez une table et que vous l'utilisez en interne, il est clair que le les types sont, et lorsque vous marshall le JSON, il est clair que vous excluez certains types et où cette exclusion a lieu.

    type Player struct {
        Id        int64     `json:"id"`
        Username  string    `json:"username,omitempty"`
        Password  string    `json:"-,omitempty"`
        Email     string    `json:"email,omitempty"`
        Birthdate time.Time `json:"birthdate,omitempty"`
        Avatar    string    `json:"avatar,omitempty"`
    }
    
    func (p Player) PublicInfo() map[string]interface{} {
        return map[string]interface{}{
            "id":       p.Id,
            "username": p.Username,
            "avatar":   p.Avatar,
        }
    }
    

    Regardez-le en action: https://play.golang.org/p/24t-B6ZuUKu


    0 commentaires

    1
    votes

    Si vous souhaitez représenter une version publique et privée des données - et qu'une version est un sur-ensemble de l'autre, essayez structures intégrées . L'ajout d'un marshaller JSON personnalisé et vous pouvez obtenir deux présentations des mêmes données de base.

    // public info
    type PublicPlayer struct {
            Id       int64  `json:"id"`
            Username string `json:"username,omitempty"`
            Avatar   string `json:"avatar,omitempty"`
    }
    
    // private info
    type Player struct {
            PublicPlayer // embed public info
    
            Password  string    `json:"password,omitempty"`
            Email     string    `json:"email,omitempty"`
            Birthdate time.Time `json:"birthdate,omitempty"`
    }
    
    type Table struct {
        Id           int    `json:"id"`
        PlayerTop    Player `json:"playerTop"`
        PlayerBottom Player `json:"playerBottom"`
    }
    
    // derivative type, so we can add a custom marshaller
    type PublicTable Table
    
    func (t PublicTable) MarshalJSON() ([]byte, error) {
            return json.Marshal(
                    // anonymous struct definition
                    struct {
                            Id     int          `json:"id"`
                            Top    PublicPlayer `json:"playerTop"`
                            Bottom PublicPlayer `json:"playerBottom"`
                    }{  
                            t.Id,
                            t.PlayerTop.PublicPlayer,    // only export public data
                            t.PlayerBottom.PublicPlayer, // only export public data
                    },  
            )   
    }
    

    Exécuter dans terrain de jeu :

    Database JSON: {"Id":12345,"PlayerTop":{"id":456,"username":"Peter","avatar":"peter.png","password":"Secr3t","birthdate":"0001-01-01T00:00:00Z"},"PlayerBottom":{"id":890,"username":"Paul","avatar":"paul.png","password":"abc123","birthdate":"0001-01-01T00:00:00Z"}}
    
    Public JSON: {"id":12345,"playerTop":{"id":456,"username":"Peter","avatar":"peter.png"},"playerBottom":{"id":890,"username":"Paul","avatar":"paul.png"}}
    


    0 commentaires