作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝! cnblogs博客 zhihu Github 公眾號:一本正經的瞎扯 首先,我希望所有golang中用於http請求響應的結構,都使用proto3來定義。 麻煩的是,有的情況下某個欄位的類型可能是動態的,對應的JSON類型可能是 ...
作者:張富春(ahfuzhang),轉載時請註明作者和引用鏈接,謝謝!
首先,我希望所有golang中用於http請求響應的結構,都使用proto3來定義。
麻煩的是,有的情況下某個欄位的類型可能是動態的,對應的JSON類型可能是number/string/boolean/null中的其中一種。
一開始我嘗試用proto.Any類型,就像這樣:
import "google/protobuf/any.proto";
message MyRequest{
google.protobuf.Any user_input = 1; // 用戶可能輸入 number / string / boolean / null 中的其中一種
}
使用protoc生成代碼後,發現這玩意兒完全沒辦法做json的encode/decode。
理想的辦法是讓生成golang代碼中的 user_input 成為 interface{} 類型。但如何才能讓proto3生成golang的interface類型呢?
嘗試後發現可以用下麵的辦法解決:
1.使用gogo proto的擴展語法
import "google/protobuf/descriptor.proto";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
message MyRequest{
bytes user_input = 1[(gogoproto.customtype) = "InterfaceType"]; // 使用一個叫做 InterfaceType 的自定義類型
}
註意:InterfaceType 直接寫成 interface{} 是不行的。因為 interface{} 類型沒有實現序列化的介面。
執行protoc後生成瞭如下代碼:
type MyRequest struct {
UserInput []InterfaceType `protobuf:"bytes,4,rep,name=user_input,proto3,customtype=InterfaceType" json:"user_input,omitempty"`
}
2. 編寫 InterfaceType 類型對應的序列化代碼
// interface_type.go
// 放在xxx.pb.go的同一目錄下
package proto
import (
"encoding/json"
"errors"
)
type InterfaceType struct {
Value interface{}
}
func (t InterfaceType) Marshal() ([]byte, error) {
return nil, errors.New("not implement")
}
func (t *InterfaceType) MarshalTo(data []byte) (n int, err error) {
return 0, errors.New("not implement")
}
func (t *InterfaceType) Unmarshal(data []byte) error {
return errors.New("not implement")
}
func (t *InterfaceType) Size() int {
return -1
}
// 因為只做JSON的序列化,所以只實現這兩個方法就行了
func (t InterfaceType) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Value)
}
func (t *InterfaceType) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &t.Value)
}
3.測試一下
// my_request.pb_test.go
package proto
import (
"encoding/json"
"testing"
)
func Test_MyRequest(t *testing.T) {
j := `{"user_input":123}`
inst := &MyRequest{}
err := json.Unmarshal([]byte(j), inst)
if err != nil {
t.Errorf("json decode error, err=%+v", err)
return
}
t.Logf("%+v", MyRequest)
str, err := json.Marshal(inst)
if err != nil {
t.Errorf("json encode error, err=%+v", err)
return
}
t.Logf("json=%s", string(str))
}
序列化和反序列化的結果一致。
具體細節請參考這個鏈接:https://github.com/gogo/protobuf/blob/master/custom_types.md
have fun.