用protobuf生成json结构插件实现

栏目: 服务器 · 发布时间: 5年前

内容简介:在日常中json数据格式应用场景很多。比如在restful请求/返回、业务通信协议、消息(nsq/kafka),广泛使用json。但是目前存在如下问题考虑到golang里可以直接根据struct序列化成json,我们很自然的想到的是通过生成struct结构来实现。想想常用的描述协议的2个格式(protobuf、thrift),也可以用来生成struct结构。这里考虑到thrift改动太大,用protobuf描述更好些。一个描述示例如下:

背景

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地址

https:// github.com/buptbill220/ protobuf/tree/master

支持特性

  • 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"}]}
*/

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

树莓派学习指南

树莓派学习指南

[英]Peter Membrey、[澳]David Hows / 张志博、孙峻文 / 人民邮电出版社 / 2014-4 / 49.00元

树莓派(Raspberry Pi)是一款基于Linux系统的、只有一张信用卡大小的卡片式计算机。由于功能强大、性能出色、价格便宜等特点,树莓派得到了计算机硬件爱好者以及教育界的欢迎,风靡一时。 《树莓派学习指南(基于Linux)》是学习在树莓派上基于Linux进行开发的一本实践指南。全书共3个部分11章,第一部分是前两章,讲述如何设置和运行图形用户界面(GUI)。第二部分是第3章到第7章,讲......一起来看看 《树莓派学习指南》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具