用protobuf生成json结构插件实现

buptbill220 · · 872 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

背景

json格式不便描述/统一管理

在日常中json数据格式应用场景很多。比如在restful请求/返回、业务通信协议、消息(nsq/kafka),广泛使用json。但是目前存在如下问题

  • json数据协议分散在代码里,没有一个统一的描述方式
  • json使用往往使用map结构,然后序列化。造成上下游使用混乱,容易出错
  • 如果使用struct方式定义,那么需要手动写代码,很是繁琐
    比如以下示例:
json格式如下:
{
    "id": 123,
    "name": "fangming",
    "age": 18,
    "occ": {"id": 111, "industry": "professor"},
    "occs": [
        {"id": 111, "industry": "professor"},    
        {"id": 222, "industry": "engineer"}
    ],
}
为了描述他我们不得不定义一个结构,然后序列化
type User struct {
    Id   int64         `json:"id"`
    Name string        `json:"name"`
    Age  int32         `json:"age"`
    Occ  *Occupation   `json:"occ"`
    Occs []*Occupation `json:"occs"`
}
type Occupation struct {
    Id       int64  `json:"id"`
    Industry string `json:"industry"`
}

方案

用protobuf协议描述json

考虑到golang里可以直接根据struct序列化成json,我们很自然的想到的是通过生成struct结构来实现。想想常用的描述协议的2个格式(protobuf、thrift),也可以用来生成struct结构。这里考虑到thrift改动太大,用protobuf描述更好些。
一个描述示例如下:

message User {
    required int64 id = 1;
    required string name = 2;
    required int32 age = 3;
    optional Occupation occ = 4;
    repeated Occupation occs = 5;
}
message Occupation {
    required int64 id = 1;
    required string industry = 2;
}

// 代表的json结构如下
{
    "id": 123,
    "name": "fangming",
    "age": 18,
    "occ": {"id": 111, "industry": "professor"},
    "occs": [
        {"id": 111, "industry": "professor"},
        {"id": 222, "industry": "engineer"}
    ],
}

实现方案

直接基于protoc支持的扩展功能,写扩展插件即可。这里开发自己的插件:gogofmqjson

实现细节

后续有时间再更新

git地址

github.com/buptbill220/

支持特性

  • json编码完整支持required、optional字段语义检测;required字段不填序列化/反序列化报错
  • 比用protobuf本身生成更简洁的代码
  • 生成支持json的marshal & unmarshal方法
  • 支持Set方法(主要是解决成员变量为指针类型,需要手动使用&)
  • Set方法支持builder模式

protobuf其他特性

  • 支持Set方法(主要是解决成员变量为指针类型,需要手动使用&)
  • Set方法支持builder模式

安装使用

json协议代码生成

无缝迁移gogo

生成代码示例

// FmqJsonCode generated by protoc-gen-gogofmqjson. Do NOT EDIT
// Author: fangming
// Email: fangming@bytedance.com
// source: test.proto
// It is generated from these files:
// 	test.proto

// It has these top-level messages:
//== 	User
//== 	Occupation

package test

import (
	encoding_json "encoding/json"
	fmt "fmt"
)

type User struct {
	Id   *int64        `json:"id"`
	Name *string       `json:"name"`
	Age  *int32        `json:"age"`
	Occ  *Occupation   `json:"occ"`
	Occs []*Occupation `json:"occs"`
}

func (m *User) Reset()      { *m = User{} }
func (*User) ProtoMessage() {}

func (m *User) GetId() int64 {
	if m != nil && m.Id != nil {
		return *m.Id
	}
	return 0
}

func (m *User) SetId(v int64) *User {
	if m != nil {
		m.Id = &v
	}
	return m
}

func (m *User) GetName() string {
	if m != nil && m.Name != nil {
		return *m.Name
	}
	return ""
}

func (m *User) SetName(v string) *User {
	if m != nil {
		m.Name = &v
	}
	return m
}

func (m *User) GetAge() int32 {
	if m != nil && m.Age != nil {
		return *m.Age
	}
	return 0
}

func (m *User) SetAge(v int32) *User {
	if m != nil {
		m.Age = &v
	}
	return m
}

func (m *User) GetOcc() *Occupation {
	if m != nil {
		return m.Occ
	}
	return nil
}

func (m *User) SetOcc(v *Occupation) *User {
	if m != nil {
		m.Occ = v
	}
	return m
}

func (m *User) GetOccs() []*Occupation {
	if m != nil {
		return m.Occs
	}
	return nil
}

func (m *User) SetOccs(v []*Occupation) *User {
	if m != nil {
		m.Occs = v
	}
	return m
}

type Occupation struct {
	Id       *int64  `json:"id"`
	Industry *string `json:"industry"`
}

func (m *Occupation) Reset()      { *m = Occupation{} }
func (*Occupation) ProtoMessage() {}

func (m *Occupation) GetId() int64 {
	if m != nil && m.Id != nil {
		return *m.Id
	}
	return 0
}

func (m *Occupation) SetId(v int64) *Occupation {
	if m != nil {
		m.Id = &v
	}
	return m
}

func (m *Occupation) GetIndustry() string {
	if m != nil && m.Industry != nil {
		return *m.Industry
	}
	return ""
}

func (m *Occupation) SetIndustry(v string) *Occupation {
	if m != nil {
		m.Industry = &v
	}
	return m
}

func (m *User) Marshal() ([]byte, error) {
	if m == nil {
		return nil, fmt.Errorf("msg User is nil")
	}
	if err := m.Validate(); err != nil {
		return nil, err
	}
	return encoding_json.Marshal(m)
}

func (m *User) Unmarshal(data []byte) error {
	if m == nil {
		return fmt.Errorf("msg User is nil")
	}
	if err := encoding_json.Unmarshal(data, m); err != nil {
		return err
	}
	if err := m.Validate(); err != nil {
		return err
	}
	return nil
}

func (m *Occupation) Marshal() ([]byte, error) {
	if m == nil {
		return nil, fmt.Errorf("msg Occupation is nil")
	}
	if err := m.Validate(); err != nil {
		return nil, err
	}
	return encoding_json.Marshal(m)
}

func (m *Occupation) Unmarshal(data []byte) error {
	if m == nil {
		return fmt.Errorf("msg Occupation is nil")
	}
	if err := encoding_json.Unmarshal(data, m); err != nil {
		return err
	}
	if err := m.Validate(); err != nil {
		return err
	}
	return nil
}

func (m *User) Validate() error {
	if m == nil {
		return fmt.Errorf("msg User is nil")
	}
	if m.Id == nil {
		return fmt.Errorf("required field User.Id is nil")
	}
	if m.Name == nil {
		return fmt.Errorf("required field User.Name is nil")
	}
	if m.Age == nil {
		return fmt.Errorf("required field User.Age is nil")
	}
	if m.Occ != nil {
		if err := m.Occ.Validate(); err != nil {
			return err
		}
	}
	for _, p := range m.Occs {
		if err := p.Validate(); err != nil {
			return err
		}
	}
	return nil
}

func (m *Occupation) Validate() error {
	if m == nil {
		return fmt.Errorf("msg Occupation is nil")
	}
	if m.Id == nil {
		return fmt.Errorf("required field Occupation.Id is nil")
	}
	if m.Industry == nil {
		return fmt.Errorf("required field Occupation.Industry is nil")
	}
	return nil
}

测试使用/示例

func testUser() {
	user := &User{}
	data, err := user.Marshal()
	if err != nil {
		fmt.Printf("1: user marshal err %s\n", err.Error())
	}
	fmt.Printf("1: user marshal data %s\n", string(data))
	
	
	data, err = user.SetId(1).Marshal()
	if err != nil {
		fmt.Printf("2: user marshal err %s\n", err.Error())
	}
	fmt.Printf("2: user marshal data %s\n", string(data))
	
	data, err = user.SetAge(28).Marshal()
	if err != nil {
		fmt.Printf("3: user marshal err %s\n", err.Error())
	}
	fmt.Printf("3: user marshal data %s\n", string(data))
	
	data, err = user.SetName("fangming").Marshal()
	if err != nil {
		fmt.Printf("4: user marshal err %s\n", err.Error())
	}
	fmt.Printf("4: user marshal data %s\n", string(data))
	
	occ := &Occupation{}
	occ.SetId(1)
	data, err = user.SetOcc(occ).Marshal()
	if err != nil {
		fmt.Printf("5: user marshal err %s\n", err.Error())
	}
	fmt.Printf("5: user marshal data %s\n", string(data))
	
	occ.SetIndustry("computer")
	data, err = user.SetOcc(occ).Marshal()
	if err != nil {
		fmt.Printf("6: user marshal err %s\n", err.Error())
	}
	fmt.Printf("6: user marshal data %s\n", string(data))
	
	occs := []*Occupation{}
	occ = &Occupation{}
	occ.SetId(2)
	occs = append(occs, occ)
	data, err = user.SetOccs(occs).Marshal()
	if err != nil {
		fmt.Printf("7: user marshal err %s\n", err.Error())
	}
	fmt.Printf("7: user marshal data %s\n", string(data))
	
	occ.SetIndustry("teacher")
	occs = append(occs, occ)
	data, err = user.SetOccs(occs).Marshal()
	if err != nil {
		fmt.Printf("8: user marshal err %s\n", err.Error())
	}
	fmt.Printf("8: user marshal data %s\n", string(data))
}

/* 输出如下
1: user marshal err required field User.Id is nil
1: user marshal data
2: user marshal err required field User.Name is nil
2: user marshal data
3: user marshal err required field User.Name is nil
3: user marshal data
4: user marshal data {"id":1,"name":"fangming","age":28,"occ":null,"occs":null}
5: user marshal err required field Occupation.Industry is nil
5: user marshal data
6: user marshal data {"id":1,"name":"fangming","age":28,"occ":{"id":1,"industry":"computer"},"occs":null}
7: user marshal err required field Occupation.Industry is nil
7: user marshal data
8: user marshal data {"id":1,"name":"fangming","age":28,"occ":{"id":1,"industry":"computer"},"occs":[{"id":2,"industry":"teacher"},{"id":2,"industry":"teacher"}]}
*/ 


有疑问加站长微信联系(非本文作者)

本文来自:知乎专栏

感谢作者:buptbill220

查看原文:用protobuf生成json结构插件实现

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

872 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传