Modifier dynamiquement le schéma JSON dans Go with gob

Changer de manière significative la marshalisation de la structure dans json n'est possible que par la méthode MarshalJSON (), en y écrivant l'implémentation complète de la marshalisation. Comment exactement? La documentation de Go ne donne aucune réponse ou recommandation à ce sujet, offrant, pour ainsi dire, une liberté totale. Et comment profiter de cette liberté pour ne pas empiler un tas de béquilles, en transférant toute la logique à MarshalJSON (), puis ne pas écraser cette pauvre fonction en constante augmentation avec les prochaines personnalisations json?


La solution est en fait simple:


  1. Soyez honnête (honnête).

(Le deuxième point ne sera pas, le premier suffit.)


C'est cette approche qui permettra d'économiser une tonne de code déroutant govnokoda , beaucoup de modifications et un certain genre de plaisir avant une sortie importante. Examinons non pas l'exemple de la documentation, où json est personnalisé pour un simple int, et toute la logique du modèle sur plusieurs lignes, mais notre tâche d'origine.


Avons-nous vraiment besoin de changer la structure de nos installations et de bourrer un tas de béquilles? Est-ce vraiment que la rigueur du langage, qui prévoit une correspondance biunivoque entre les attributs json et la structure elle-même, a soudainement commencé à interférer avec nous?


La tâche initiale consiste à obtenir de telles structures JSON de certains formats approuvés. Dans le problème d'origine, rien n'est dit sur les béquilles. On parle de différentes structures de données. Et nous utilisons le même type de données (struct) pour stocker ces données. Ainsi, notre entité unique doit avoir plusieurs représentations. Nous avons donc eu la bonne interprétation du problème.


Nous devons faire plusieurs représentations pour notre type de données. Ne modifiez pas la conversion en json pour un cas spécifique, mais disposez en principe de plusieurs vues, dont l'une est la vue par défaut.


Donc, nous avons une autre entité - la représentation .


Et passons aux exemples et, en fait, au code.


Supposons que nous ayons une librairie qui vend des livres. Tout est construit sur des microservices, et l'un d'eux donne des données sur les demandes au format json. Les livres ont d'abord été téléchargés uniquement dans la vitrine. Ensuite, nous nous connectons à divers réseaux d'affiliation et, par exemple, nous fournissons des livres pour les étudiants universitaires à un prix spécial. Et récemment, nos spécialistes du marketing ont soudainement décidé de tenir une sorte d'actions de promotion et ils ont également besoin de leur propre prix et d'un autre texte de leur choix. Laissez un autre microservice être impliqué dans le calcul des prix et la préparation des textes, ce qui ajoute des données toutes faites à une base de données.


Ainsi, l'évolution de notre modèle de livre a atteint une telle honte:


type Book struct { Id int64 Title string Description string Partner2Title string Price int64 PromoPrice int64 PromoDescription string Partner1Price int64 Partner2Price int64 UpdatedAt time.Time CreatedAt time.Time view BookView } 

Le dernier attribut (vue) n'est pas exporté (privé), il ne fait pas partie des données, mais est l'emplacement de stockage de la vue même , qui contient des informations dans lesquelles json l'objet sera plié. Dans le cas le plus simple, il s'agit simplement de l'interface {}


 type BookView interface{} 

Nous pouvons également ajouter une méthode à l'interface de notre vue, par exemple Prepare (), qui sera appelée dans MarshalJSON () et préparer, valider ou consigner la structure de sortie.


Décrivons maintenant nos vues et la fonction elle-même


 type SiteBookView struct { Id int64 `json:"sku"` Title string `json:"title"` Description string `json:"description"` Price int64 `json:"price"` } type Partner1BookView struct { Id int64 `json:"bid"` Title string `json:"title"` Partner1Price int64 `json:"price"` } type Partner2BookView struct { Id int64 `json:"id"` Partner2Title string `json:"title"` Description string `json:"description"` Partner2Price int64 `json:"price"` } type PromoBookView struct { Id int64 `json:"ref"` Title string `json:"title"` Description string `json:"description"` PromoPrice int64 `json:"price"` PromoDescription string `json:"promo,omitempty"` } func (b Book) MarshalJSON() (data []byte, err error) { // ,    if b.view == nil { // ,      b.SetDefaultView() } //        var buff bytes.Buffer //   ,            enc := gob.NewEncoder(&buff) //  ,      ,    dec := gob.NewDecoder(&buff) //     err = enc.Encode(b) if err != nil { return } //     err = dec.Decode(b.view) if err != nil { return } //    return json.Marshal(b.view) } 

L'envoi et la réception de données entre les structures se fait selon le principe de correspondance des noms des attributs, tandis que les types n'ont pas à correspondre exactement, par exemple, vous pouvez envoyer à partir d'int64, mais l'accepter en int, mais pas en uint.


La dernière étape consiste à rassembler la vue des données installées en utilisant toute la puissance de la description standard via les balises json (`json:"promo,omitempty"`)


Une exigence très importante pour l'application de cette approche est l'enregistrement obligatoire des structures et des mappages de modèles. Pour vous assurer que toutes les structures sont toujours garanties d'être enregistrées, ajoutez-les à la fonction init ().


 func init() { gob.Register(Book{}) gob.Register(SiteBookView{}) gob.Register(Partner1BookView{}) gob.Register(Partner2BookView{}) gob.Register(PromoBookView{}) } 

Code complet du modèle:


Texte masqué
 import ( "bytes" "encoding/gob" "encoding/json" "time" ) func init() { gob.Register(Book{}) gob.Register(SiteBookView{}) gob.Register(Partner1BookView{}) gob.Register(Partner2BookView{}) gob.Register(PromoBookView{}) } type BookView interface{} type Book struct { Id int64 Title string Description string Partner2Title string Price int64 PromoPrice int64 PromoDescription string Partner1Price int64 Partner2Price int64 UpdatedAt time.Time CreatedAt time.Time view BookView } type SiteBookView struct { Id int64 `json:"sku"` Title string `json:"title"` Description string `json:"description"` Price int64 `json:"price"` } type Partner1BookView struct { Id int64 `json:"bid"` Title string `json:"title"` Partner1Price int64 `json:"price"` } type Partner2BookView struct { Id int64 `json:"id"` Partner2Title string `json:"title"` Description string `json:"description"` Partner2Price int64 `json:"price"` } type PromoBookView struct { Id int64 `json:"ref"` Title string `json:"title"` Description string `json:"description"` PromoPrice int64 `json:"price"` PromoDescription string `json:"promo,omitempty"` } func (b *Book) SetDefaultView() { b.SetSiteView() } func (b *Book) SetSiteView() { b.view = &SiteBookView{} } func (b *Book) SetPartner1View() { b.view = &Partner1BookView{} } func (b *Book) SetPartner2View() { b.view = &Partner2BookView{} } func (b *Book) SetPromoView() { b.view = &PromoBookView{} } func (b Book) MarshalJSON() (data []byte, err error) { if b.view == nil { b.SetDefaultView() } var buff bytes.Buffer enc := gob.NewEncoder(&buff) dec := gob.NewDecoder(&buff) err = enc.Encode(b) if err != nil { return } err = dec.Decode(b.view) if err != nil { return } return json.Marshal(b.view) } 


Le contrôleur aura quelque chose comme ce code:


 func GetBooksForPartner2(ctx *gin.Context) { books := LoadBooksForPartner2() for i := range books { books[i].SetPartner2View() } ctx.JSON(http.StatusOK, books) } 

Maintenant, pour un «json» de plus, ajoutez simplement une autre vue et n'oubliez pas de l'enregistrer dans init ().

Source: https://habr.com/ru/post/fr449090/


All Articles