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.
5 Réponses :
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.
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 où 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.
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 >
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>
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).
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:
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
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"}}
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