Cambiar significativamente la clasificación de la estructura en json solo es posible a través del método MarshalJSON (), escribiendo allí la implementación completa de la clasificación. Como exactamente La documentación de Go no da ninguna respuesta o recomendación a esto, brindando, por así decirlo, total libertad. ¿Y cómo aprovechar esta libertad para no acumular un montón de muletas, transfiriendo toda la lógica a MarshalJSON () y luego no sobrescribir esta pobre función de crecimiento constante con las próximas personalizaciones de json?
La solución es realmente simple:
- Se honesto (honesto).
(El segundo punto no será, el primero es suficiente).
Es este enfoque el que ahorrará una tonelada de código confuso de govnokoda , muchas modificaciones y un cierto tipo de diversión antes de un lanzamiento importante. No veamos el ejemplo en la documentación, donde json está personalizado para un int simple, y toda la lógica del modelo en varias líneas, sino en nuestra tarea original.
¿Realmente necesitamos cambiar la estructura de nuestras instalaciones y meter un montón de muletas? ¿Es realmente que el rigor del lenguaje, que proporciona una correspondencia uno a uno entre los atributos json y la estructura misma, de repente comenzó a interferir con nosotros?
La tarea inicial es obtener tales estructuras JSON de algunos formatos aprobados. En el problema original no se dice nada sobre las muletas. Se dice sobre diferentes estructuras de datos. Y usamos el mismo tipo de datos (estructura) para almacenar estos datos. Por lo tanto, nuestra entidad individual debe tener varias representaciones. Entonces obtuvimos la interpretación correcta del problema.
Necesitamos hacer varias representaciones para nuestro tipo de datos. No cambie la conversión a json para un caso específico, pero en principio tiene varias vistas, una de las cuales es la vista predeterminada.
Entonces, tenemos otra entidad: la representación .
Y vamos a los ejemplos y, en realidad, al código.
Supongamos que tenemos una librería que vende libros. Todo está construido en microservicios, y uno de ellos proporciona datos sobre solicitudes en formato json. Los libros se subieron primero solo a la tienda. Luego nos unimos a varias redes de afiliados y, por ejemplo, proporcionamos libros para estudiantes universitarios a un precio especial. Y recientemente, nuestros especialistas en marketing de repente decidieron realizar algún tipo de campañas de promoción y también necesitan su propio precio y algún otro texto propio. Deje que algún otro microservicio participe en el cálculo de precios y la preparación de textos, que agrega datos ya preparados a una base de datos.
Entonces, la evolución de nuestro modelo de libro ha alcanzado tal desgracia:
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 }
El último atributo (vista) no se exporta (privado), no es parte de los datos, pero es la ubicación de almacenamiento de la vista misma , que contiene información en la que se plegará el objeto. En el caso más simple, esto es solo la interfaz {}
type BookView interface{}
También podemos agregar algún método a la interfaz de nuestra vista, por ejemplo Prepare (), que se llamará en MarshalJSON () y de alguna manera prepara, valida o registra la estructura de salida.
Ahora describamos nuestros puntos de vista y la función misma
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) {
El envío y la recepción de datos entre estructuras se realiza de acuerdo con el principio de hacer coincidir los nombres de los atributos, mientras que los tipos no tienen que coincidir exactamente, por ejemplo, puede enviar desde int64, pero aceptarlo en int, pero no en uint.
El último paso es reunir la vista de datos instalada usando toda la potencia de la descripción estándar mediante etiquetas json (`json:"promo,omitempty"`)
Un requisito muy importante para aplicar este enfoque es el registro obligatorio de estructuras y mapeos modelo. Para garantizar que todas las estructuras siempre estén garantizadas para ser registradas, agréguelas a la función init ().
func init() { gob.Register(Book{}) gob.Register(SiteBookView{}) gob.Register(Partner1BookView{}) gob.Register(Partner2BookView{}) gob.Register(PromoBookView{}) }
Código de 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) }
El controlador tendrá algo como este código:
func GetBooksForPartner2(ctx *gin.Context) { books := LoadBooksForPartner2() for i := range books { books[i].SetPartner2View() } ctx.JSON(http.StatusOK, books) }
Ahora para un cambio de json "uno más", simplemente agregue otra vista y recuerde registrarlo en init ().