Goでprotobufの定義をimportせずにフィールドの値を使う
以下のようなコードでreq
にあるフィールドを使いたい場合、
opt := grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { // ここでreqから特定のフィールド値を取得したい return nil })
型が定義されているパッケージをインポートして、
if v, ok := req.(*pb.Req); ok { // v.Bodyでフィールドにアクセスできる }
のように変換することになる。
この時、インポートしたくない or インポートできないのであれば、protoc
が生成する定義にはGetterも同時に生成されるため、対象フィールドのGetterがあるinterfaceで型アサーションすると手軽にできる。
if v, ok := req.(interface{ GetBody() []byte }); ok { // v.GetBody()でフィールドの値を取得できる }
あまり使い所はないかもしれないが、google.golang.org/appengineは定義がinternal配下にあるため、例えばテストでWithAPICallFuncを使う際など、以下のようにしてtaskqueue.Addのリクエスト(TaskQueueAddRequest)からフィールドの値を取得できる。
var got []byte ctx = appengine.WithAPICallFunc(ctx, func(ctx context.Context, service, method string, in, out proto.Message) error { if service == "taskqueue" && method == "Add" { if v, ok := in.(interface{ GetBody() []byte }); ok { got = v.GetBody() } } return nil }) DoSomething(ctx) // 関数内でtaskqueue.Add()が呼ばれる if !reflect.DeepEqual(got, want) { t.Errorf("body = %q, want %q", got, want) }
なお、フィールドがインポートできない型になっているとinterfaceも作れないのでこの方法は使えない。
それでも必要な場合はencoding/json
パッケージでMarshal
してからmap[string]interface{}
や独自型にUnmarshal
すれば良い。