/ Vlastná serializácia a deserializácia JSON pre rozhrania Go - json, go, serializácia

Vlastná serializácia a deserializácia JSON pre rozhranie Go - json, go, serializácia

Momentálne som vyvíjal rozhranie JSON API pre bloggolang a ja som narazil na prekážku, ktorá sa snažila zvládnuť serializáciu a deserializáciu blogových príspevkov Chcem, aby moje príspevky obsahovali pole pozícií, ktoré by mohli obsahovať množstvo vecí (napríklad normálne odseky, obrázky, úvodzovky atď. .) Používam Mongo na ukladanie (s úžasným mgo knižnice) a chcem tieto príspevky uložiť takto:

{
"title": "Blog post",
"sections": [
{
"type": "text",
"content": { "en": "English content", "de": "Deutscher Inhalt" }
},
{
"type": "image",
"content": "https://dummyimage.com/100x100"
},
...more sections
],
...other fields
}

Snažil som sa niekoľko riešení, aby som to urobil, a nikto naozaj nevyzeral ako "správna cesta", aby to urobil:

  1. Nezaťažuje sa obsah

Toto sa zdalo byť zrejmým riešením, len pomocou jednoduchého štruktúry:

type PostSection struct{
Type    string
Content interface{}
}

Týmto spôsobom môžem prejsť cez čokoľvek frontend POSTS a uložiť ho. Avšak manipulácia s údajmi alebo ich validácia sa stáva nemožným, takže to nie je dobré riešenie.

  1. Používanie serializácie vlastného rozhrania

našiel som tento článok o serializácii rozhraní v golangu. Zdá sa, že to bolo na prvý pohľad skvelé, pretože by som mohol mať takéto rozhranie:

type PostSection interface{
Type()    string
Content() interface{}
}

a potom implementovať každý typ takto:

type PostImage string

func (p *PostImage) Type() string {
return "image"
}

func (p *PostImage) Content() interface{} {
return p
}

Optimálne by to bolo "a bolo to po realizácii MarshalJSON a UnmarshalJSON pre všetky moje typy to fungovalo dobre pri použití json.Marshal priamo na PostSection objekt.

Pri serializácii alebo deserializácii celého objektu Post obsahujúceho pole PostSections, môj vlastný kód bol práve ignorovaný a PostSections by boli považované len za základné objekty (string alebo map[string]string v príkladoch) pri serializácii alebo výsledkom prázdnych objektov pri deserializácii.

  1. Písanie vlastnej serializácie pre celú štruktúru Post

Takže riešenie, ktoré v súčasnosti používam, ale bolo byako zmeniť je vlastná serializácia pre celý objekt Post. To vedie k veľmi škaredému kódu, pretože naozaj potrebujem len vlastný kód pre jedno pole a tak prechádzať ostatnými, takže deserializácia vyzerá podobne ako toto:

p.ID = decoded.ID
p.Author = decoded.Author
p.Title = decoded.Title
p.Intro = decoded.Intro
p.Slug = decoded.Slug
p.TitleImage = decoded.TitleImage
p.Images = decoded.Images
...more fields...

a potom dekódovanie týchto častí:

sections := make([]PostSection, len(decoded.Sections))
for i, s := range decoded.Sections {
if s["type"] == "text" {
content := s["content"].(map[string]interface{})
langs := make(PostText, len(content))
for lang, langContent := range content {
langString := langContent.(string)
langs[lang] = langString
}
sections[i] = &langs
} else if s["type"] == "image" {
content := s["content"].(string)
contentString := PostImage(content)
sections[i] = &contentString
}
}

p.Sections = sections

Je to veľa kódu, ktorý budem musieť používaťzakaždým, keď chcem zahrnúť PostSections do inej podoby inde (napríklad v Newsletteri) a nemám pocit, že idiomatic go kód dlhým zásahom.Tiež nie je žiadna chyba zaobchádzanie pre zlomené časti - oni len spôsobiť panika ako tento.

Existuje čisté riešenie tohto problému?

odpovede:

2 pre odpoveď č. 1

Aby ste sa vyhli písaniu UnmarshalJSON pre celok Post môžete zabaliť PostSection v konkrétnom type a má implementovať rozhranie Unmarshaler.

type Post struct {
ID         int
Author     string
Title      string
Intro      string
Slug       string
TitleImage string
Images     []string

Sections []*PostSection
}

type SectionContent interface {
Type()    string
Content() interface{}
}

type PostSection struct {
Content SectionContent
}

func (s *PostSection) UnmarshalJSON(data []byte) error {
// ...
return nil
}