简体   繁体   English

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

[英]Custom JSON serialization and deserialization for interfaces in Go

I'm currently developing a JSON API for a blog in golang, and I've run into a roadblock trying to handle the serialization and deserialization of blog posts. 我目前正在为golang中的博客开发JSON API,并且遇到了处理博客文章的序列化和反序列化的障碍。 I want my posts to contain an array of Post Sections that could be a number of things (such as normal paragraphs, images, quotes, etc.). 我希望我的帖子包含一系​​列帖子部分,这些部分可以是很多东西(例如普通段落,图像,引号等)。 I'm using Mongo for storage (with the amazing mgo library ) and I want to save the posts like this: 我正在使用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
}

I've tried several solutions to implement this in go and none have really seemed like the "right way" to do it: 我已经尝试了几种解决方案来实现此目标,但似乎没有一种真正的“正确方法”:

  1. Not caring about the content 不关心内容

This seemed like the obvious solution, just using a simple struct: 这似乎是显而易见的解决方案,仅使用简单的结构即可:

type PostSection struct{
  Type    string
  Content interface{}
}

This way, I can pass through whatever the frontend POSTS and save it. 这样,我可以遍历任何前端POSTS并保存它。 However, manipulating the data or validating it becomes impossible, so it's not a good solution. 但是,操作或验证数据变得不可能,因此这不是一个好的解决方案。

  1. Using custom interface serialization 使用自定义接口序列化

I found this article about serializing interfaces in golang. 我发现这篇关于在golang中序列化接口的文章。 This seemed great at first, because I could have an interface like this: 一开始这看起来很棒,因为我可以有一个像这样的界面:

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

and then implement every type like this: 然后实现这样的每种类型:

type PostImage string

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

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

Optimally, that would've been it, and after implementing MarshalJSON and UnmarshalJSON for all my types, it was working fine when using json.Marshal directly on a PostSection object. 理想的情况就是这样,在为我所有的类型实现MarshalJSONUnmarshalJSON之后,直接在PostSection对象上使用json.Marshal时,它运行良好。

However, when serializing or deserializing an entire Post object containing an array of PostSection s, my custom code was just being ignored and the PostSections would just be treated as the underlying objects ( string or map[string]string in the examples) when serializing, or result in empty objects when deserializing. 但是,当对包含PostSection数组的整个Post对象进行序列化或反序列化时,我的自定义代码将被忽略,并且在序列化时,PostSections将仅被视为基础对象(示例中为stringmap[string]string ),或反序列化时导致空对象。

  1. Writing custom serialization for the entire Post struct 为整个Post结构编写自定义序列化

So, the solution I'm currently using but would like to change is custom serialization for the entire Post object. 因此,我当前正在使用但想更改的解决方案是针对整个Post对象的自定义序列化。 This leads to super ugly code, as I only really need custom code for a single field and so I'm passing through the rest, making the deserialization look similar to this: 这导致代码非常丑陋,因为我只需要单个字段的自定义代码,因此我将遍历其余部分,从而使反序列化看起来类似于以下内容:

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...

and then, decoding the sections like this: 然后,像这样解码这些部分:

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

This is a whole lot of code I'll have to use every time I wanna include PostSections in another form somewhere else (for example in a Newsletter) and it doesn't feel like idiomatic go code by a long shot. 每当我想在其他地方(例如在新闻通讯中)以另一种形式包含PostSections时,都必须使用大量的代码,而且从长远来看,它并不像惯用的go代码。 Also, there is no error handling for malformed sections - They just cause a panic like this. 另外,对于格式错误的部分也没有错误处理-它们只会引起类似的恐慌。

Is there a clean solution to this problem? 有没有解决这个问题的解决方案?

To avoid writing UnmarshalJSON for the whole Post you can wrap your PostSection in a concrete type and have it implement the Unmarshaler interface. 为了避免为整个Post编写UnmarshalJSON ,可以将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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM