【grpc-gateway】0, false, nullなどの値をJSON出力対象にする


はじめに

例えばgRPCのメソッド、メッセージが以下だとして

syntax = "proto3";

import "google/protobuf/empty.proto";
import "google/api/annotations.proto";

package proto;

service TestService {
    rpc Test (google.protobuf.Empty) returns (TestMessage) {
        option (google.api.http) = {
            get: "/test"
        };
    }
}

message TestMessage {
    int32 a = 1;
    bool b = 2;
    string c = 3;
    repeated string d = 4;
}

サーバからクライアントに送るメッセージが(極端ですが)以下のような内容だったとします。

TestMessage {
  a: 0,
  b: false,
  c: "",
  d: nil
}

grpc-gateway経由で上記のメッセージを受け取った場合、空のJSONがレスポンスとして返ってきてしまいます。しかし、本当は以下のようなレスポンスが返ってきて欲しいです。

{ "a": 0, "b": false, "c": "", "d": [] }

ということでこのような設定にする方法を調べましたが、日本語の記事が見つからなかったのでQiitaに載せておきます。

やりかた

grpc-gatewayに、ゼロ値は省略せずにJSONの項目に含めてくれるオプションがありますので、それを指定してgrpc-gatewayのサーバを起動すれば万事解決です。

opts := []runtime.ServeMuxOption{
    runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}),
}

mux := runtime.NewServeMux(opts...)

原因

そもそも、なぜJSONのレスポンスに0や空文字が含まれていないのかという話ですが、Protocol Bufferから作成されたGo用の構造体にomitemptpyが指定されているためです。

type TestMessage struct {
    A                    int32    `protobuf:"varint,1,opt,name=a,proto3" json:"a,omitempty"`
    B                    bool     `protobuf:"varint,2,opt,name=b,proto3" json:"b,omitempty"`
    C                    string   `protobuf:"bytes,3,opt,name=c,proto3" json:"c,omitempty"`
    D                    []string `protobuf:"bytes,4,rep,name=d,proto3" json:"d,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

しかも、このomitemptyはハードコードで指定されているのでprotocでコンパイルすときには必ず指定されてしまいます…

上記のような理由から、構造体に指定されているomitemptyを無視してゼロ値を出力するオプションがgrpc-gateway側で作成されたようです。ありがとうphilipithomasさん!

参考リンク

golang protobuf remove omitempty tag from generated json tags -Stack Overflow
Support emitting default values in JSON #233