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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
 "title":"Blog post",
 "sections": [
    {
     "type":"text",
     "content": {"en":"English content","de":"Deutscher Inhalt" }
    },
    {
     "type":"image",
     "content":"https://dummyimage.com/100x100"
    },
    ...more sections
  ],
  ...other fields
}

我已经尝试了几种解决方案来实现此目标,但似乎没有一种真正的"正确方法":

  • 不关心内容
  • 这似乎是显而易见的解决方案,仅使用简单的结构即可:

    1
    2
    3
    4
    type PostSection struct{
      Type    string
      Content interface{}
    }

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

  • 使用自定义接口序列化
  • 我发现这篇关于在golang中序列化接口的文章。一开始这看起来很棒,因为我可以有一个像这样的界面:

    1
    2
    3
    4
    type PostSection interface{
      Type()    string
      Content() interface{}
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    type PostImage string

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

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

    理想的情况就是这样,在为所有类型实现MarshalJSONUnmarshalJSON之后,直接在PostSection对象上使用json.Marshal时,它运行良好。

    但是,当序列化或反序列化包含PostSection数组的整个Post对象时,我的自定义代码将被忽略,并且PostSections将仅被视为基础对象(在示例中为stringmap[string]string),当序列化,或在反序列化时导致空对象。

  • 为整个Post结构编写自定义序列化
  • 因此,我当前正在使用但想更改的解决方案是针对整个Post对象的自定义序列化。这会导致代码过于丑陋,因为我只需要单个字段的自定义代码,因此我将遍历其余部分,从而使反序列化看起来与此类似:

    1
    2
    3
    4
    5
    6
    7
    8
    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...

    然后,像这样解码这些部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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

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

    有没有解决此问题的解决方案?

    • #2,when serializing or deserializing an entire Post object containing an array of PostSections, my custom code was not being used and I would get errors。 有什么错误? 您是否为Post对象本身实现了MarshalJSONUnmarshalJSON
    • 对不起,但似乎我记得那是错误的。 我只是再次尝试过,并且错误仅在尝试从mgo反序列化时出现,因为它找不到PostSection接口的SetBSON方法。 至于您的第二个问题,我将在#3中讨论。

    为了避免为整个Post编写UnmarshalJSON,您可以将PostSection包装为具体类型,并使其实现Unmarshaler接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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
    }