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すれば良い。