douxiong4892
2017-09-27 18:05
浏览 107

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

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

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

However, when serializing or deserializing an entire Post object containing an array of PostSections, 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.

  1. Writing custom serialization for the entire Post struct

So, the solution I'm currently using but would like to change is custom serialization for the entire Post object. 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. Also, there is no error handling for malformed sections - They just cause a panic like this.

Is there a clean solution to this problem?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • doufei4923 2017-09-27 19:37
    已采纳

    To avoid writing UnmarshalJSON for the whole Post you can wrap your PostSection in a concrete type and have it implement the Unmarshaler interface.

    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
    }
    
    点赞 打赏 评论

相关推荐 更多相似问题