douxiong4892 2017-09-27 18:05
浏览 112
已采纳

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
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 yolov8边框坐标
  • ¥15 matlab中使用gurobi时报错
  • ¥15 WPF 大屏看板表格背景图片设置
  • ¥15 这个主板怎么能扩出一两个sata口
  • ¥15 不是,这到底错哪儿了😭
  • ¥15 2020长安杯与连接网探
  • ¥15 关于#matlab#的问题:在模糊控制器中选出线路信息,在simulink中根据线路信息生成速度时间目标曲线(初速度为20m/s,15秒后减为0的速度时间图像)我想问线路信息是什么
  • ¥15 banner广告展示设置多少时间不怎么会消耗用户价值
  • ¥16 mybatis的代理对象无法通过@Autowired装填
  • ¥15 可见光定位matlab仿真