Alterar dinamicamente o esquema JSON no Go with gob

Mudar significativamente a marshalization da estrutura em json é possível apenas pelo método MarshalJSON (), escrevendo aí a implementação completa da marshalization. Como exatamente? A documentação da Go não fornece respostas ou recomendações para isso, fornecendo, por assim dizer, total liberdade. E como tirar proveito dessa liberdade para não empilhar muitas muletas, transferindo toda a lógica para MarshalJSON () e depois não sobrescrever essa função em constante crescimento ruim com as próximas personalizações de json?


A solução é realmente simples:


  1. Seja honesto.

(O segundo ponto não será, o primeiro é suficiente.)


É essa abordagem que economizará uma tonelada de código confuso de govnokoda , muitas alterações e um certo tipo de diversão antes de um lançamento importante. Não vamos ver o exemplo da documentação, em que json é customizado para um int simples e toda a lógica do modelo em várias linhas, mas nossa tarefa original.


Realmente precisamos mudar a estrutura de nossas instalações e enfiar um monte de muletas? É realmente que o rigor da linguagem, que fornece uma correspondência individual entre os atributos json e a própria estrutura, de repente começou a interferir conosco?


A tarefa inicial é obter essas estruturas JSON de alguns formatos aprovados. No problema original, nada é dito sobre muletas. Diz-se sobre diferentes estruturas de dados. E usamos o mesmo tipo de dados (struct) para armazenar esses dados. Assim, nossa única entidade deve ter várias representações. Portanto, obtivemos a interpretação correta do problema.


Precisamos fazer várias representações para o nosso tipo de dados. Não altere a conversão para json para um caso específico, mas, em princípio, possui várias visualizações, uma das quais é a visualização padrão.


Então, temos outra entidade - representação .


E vamos aos exemplos e, na verdade, ao código.


Suponha que tenhamos uma livraria que venda livros. Tudo é construído em microsserviços e um deles fornece dados sobre solicitações no formato json. Os livros foram enviados primeiro apenas para a vitrine. Em seguida, nos conectamos a várias redes afiliadas e, por exemplo, fornecemos livros para estudantes universitários por um preço especial. Recentemente, nossos profissionais de marketing decidiram repentinamente realizar algum tipo de ação de promoção e também precisam de seu próprio preço e de algum outro texto. Deixe que outros microsserviços se envolvam no cálculo de preços e na preparação de textos, o que adiciona dados prontos a um banco de dados.


Portanto, a evolução do nosso modelo de livro atingiu tal desgraça:


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 } 

O último atributo (visualização) não é exportado (privado), não faz parte dos dados, mas é o local de armazenamento da própria visualização , que contém informações em que json o objeto será dobrado. No caso mais simples, isso é apenas interface {}


 type BookView interface{} 

Também podemos adicionar algum método à interface da nossa exibição, por exemplo, Prepare (), que será chamado em MarshalJSON () e, de alguma forma, preparar, validar ou registrar a estrutura de saída.


Agora vamos descrever nossas visões e a própria função


 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) } 

O envio e recebimento de dados entre estruturas ocorre de acordo com o princípio de corresponder os nomes dos atributos, enquanto os tipos não precisam corresponder exatamente, por exemplo, você pode enviar do int64, mas aceitá-lo no int, mas não no uint.


O último passo é organizar a visualização de dados instalada usando toda a potência da descrição padrão via tags json (`json:"promo,omitempty"`)


Um requisito muito importante para aplicar essa abordagem é o registro obrigatório de estruturas e mapeamentos de modelos. Para garantir que todas as estruturas sempre tenham a garantia de serem registradas, adicione-as à função init ().


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

Código do modelo completo:


Texto oculto
 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) } 


O controlador terá algo parecido com este código:


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

Agora, para uma mudança no json “mais uma”, basta adicionar outra visão e lembre-se de registrá-la em init ().

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


All Articles