Go中接口的自定义JSON序列化和反序列化

hap*_*ens 2 serialization json go

我目前正在用 golang 为博客开发 JSON API,但在尝试处理博客文章的序列化和反序列化时遇到了障碍。我希望我的帖子包含一系​​列帖子部分,这些部分可以是很多东西(例如普通段落、图像、引用等)。我正在使用 Mongo 进行存储(使用令人惊叹的mgo 库),我想像这样保存帖子:

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

我已经尝试了几种解决方案来在 go 中实现这一点,但没有一个真正看起来像“正确的方法”:

  1. 不在乎内容

这似乎是显而易见的解决方案,只需使用一个简单的结构:

type PostSection struct{
  Type    string
  Content interface{}
}
Run Code Online (Sandbox Code Playgroud)

这样,我可以通过任何前端 POSTS 并保存它。但是,操纵数据或验证数据变得不可能,因此这不是一个好的解决方案。

  1. 使用自定义接口序列化

我找到了这篇关于在 golang 中序列化接口的文章。起初这看起来很棒,因为我可以有一个这样的界面:

type PostSection interface{
  Type()    string
  Content() interface{}
}
Run Code Online (Sandbox Code Playgroud)

然后像这样实现每种类型:

type PostImage string

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

func (p *PostImage) Content() interface{} {
  return p
}
Run Code Online (Sandbox Code Playgroud)

最理想的情况就是这样,在实现之后MarshalJSONUnmarshalJSON对于我的所有类型,当直接在 PostSection 对象上使用 json.Marshal 时,它工作正常。

但是,在序列化或反序列化包含PostSections数组的整个 Post 对象时,我的自定义代码只是被忽略,并且 PostSections在序列化时只会被视为底层对象(stringmap[string]string在示例中),或者在反序列化时导致空对象.

  1. 为整个 Post 结构编写自定义序列化

因此,我目前正在使用但想更改的解决方案是对整个 Post 对象进行自定义序列化。这导致了超级丑陋的代码,因为我只需要为单个字段自定义代码,所以我正在传递其余的代码,使反序列化看起来类似于:

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...
Run Code Online (Sandbox Code Playgroud)

然后,像这样解码部分:

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
Run Code Online (Sandbox Code Playgroud)

每次我想在其他地方(例如在时事通讯中)以另一种形式包含 PostSections 时,我都必须使用大量代码,而且从长远来看,它感觉不像是惯用的 go 代码。此外,对于格式错误的部分没有错误处理 - 它们只会引起像这样的恐慌。

这个问题有干净的解决方案吗?

mko*_*iva 6

为了避免编写UnmarshalJSON整体,Post您可以将您PostSection的内容包装在具体类型中并让它实现 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
}
Run Code Online (Sandbox Code Playgroud)