/ / Niestandardowa serializacja i deserializacja JSON dla interfejsów w Go - json, go, serializacja

Niestandardowa serializacja i deserializacja JSON dla interfejsów w Go - json, go, serializacja

Aktualnie pracuję nad interfejsem API JSON dla bloga wgolang, a ja natknąłem się na blokadę, próbując poradzić sobie z serializacją i deserializacją postów na blogu. Chcę, aby moje posty zawierały szereg sekcji postów, które mogłyby być wieloma rzeczami (takimi jak zwykłe akapity, obrazy, cytaty itp. .) Używam Mongo do przechowywania (z niesamowitym Biblioteka mgo) i chcę zapisać takie posty:

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

Wypróbowałem kilka rozwiązań, aby zaimplementować to w praktyce, a żadna z nich nie wydawała się "właściwą drogą":

  1. Nie troszcząc się o treść

To wydawało się oczywistym rozwiązaniem, po prostu używając prostej struktury:

type PostSection struct{
Type    string
Content interface{}
}

W ten sposób mogę przejść przez to co przednia POSTS i zapisać. Jednak manipulowanie danymi lub sprawdzanie poprawności staje się niemożliwe, więc nie jest to dobre rozwiązanie.

  1. Korzystanie z serializacji niestandardowego interfejsu

znalazłem Ten artykuł o serializowaniu interfejsów w golangu. Z początku wydawało się to świetne, ponieważ mogłem mieć taki interfejs:

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

a następnie zaimplementuj każdy taki typ:

type PostImage string

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

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

Optymalnie, byłby to i po wdrożeniu MarshalJSON i UnmarshalJSON dla wszystkich moich typów działało dobrze podczas używania json.Marshal bezpośrednio na obiekcie PostSection.

Jednak podczas serializowania lub deserializacji całego obiektu Post zawierającego tablicę PostSections, mój niestandardowy kod był po prostu ignorowany, a PostSections byłyby traktowane jako obiekty leżące u podstaw (string lub map[string]string w przykładach) podczas serializowania lub w wyniku pustych obiektów podczas deserializacji.

  1. Zapisywanie niestandardowej serializacji dla całej struktury Post

Tak więc, rozwiązanie, którego obecnie używam, ale będzieChcę zmienić serializację niestandardową dla całego obiektu Post. Prowadzi to do bardzo brzydkiego kodu, ponieważ tak naprawdę potrzebuję tylko niestandardowego kodu dla pojedynczego pola, więc przechodzę przez resztę, dzięki czemu deserializacja wygląda podobnie do tego:

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 następnie dekodowanie sekcji takich jak to:

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

To dużo kodu, którego będę musiał użyćza każdym razem, gdy chcę umieścić PostSections w innej formie gdzieś indziej (na przykład w biuletynie) i nie czuję się jak idiomatyczny kod go przez długie ujęcie, a także nie ma obsługi błędów dla zniekształconych sekcji - po prostu wywołują panikę to.

Czy istnieje czyste rozwiązanie tego problemu?

Odpowiedzi:

2 dla odpowiedzi № 1

Aby uniknąć pisania UnmarshalJSON przez cały Post możesz owinąć swoje PostSection w konkretnym rodzaju i zaimplementuj interfejs 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
}