Protocol BuffersをJSONに変換する
React Native + GoのProobuf API Serverの構成でアプリケーションを作っているのですが、次の条件でdeserializeができないことがわかってしまった。
- [javascript] Failed to deserialize protobuf message which has other message types. · Issue #3098 · google/protobuf · GitHub
- protobuf/reader.js at master · google/protobuf · GitHub
decodeIO/protobuf.js の方も一応試してみたけど、うまく動かないので急遽Protobufの使用を諦めてjsonに切り替えることにします。 こちらも詳細は↓にまとめました。あとでGithubの方にもIssue建てようかと思います
protobuf/jsonpb
でJSONに変換する
開発期間も短いので今からサーバの実装を変えていくのはできればさけたい。 モックサーバとかはすでにProtobufで書いていたので、それをJSONに変換する処理を書いていく方法を検討してみた。
今回使っている型はそんなに多くないので自前でさっと書けないかなとか考えつつ調べてみたら、protobufの中に jsonpb
という便利そうなモジュールがあった。
ProtobufのモックAPIをJSONのAPIに書き換えてみる。
func confirmProfile(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) p := &pb.ConfirmProfileResponse{ HasProfile: true, Profile: &pb.ProfileResponse{ Uid: "uid0001", Username: "c_bata_", IconUrl: "http://example.com/hoge.png", WebsiteUrl: "http://example.com", CreatedAt: 1000, UpdatedAt: 1000, }, } m := jsonpb.Marshaler{ EmitDefaults: true, Indent: " ", OrigName: true, } m.Marshal(w, p) return }
curlで確認。
$ curl localhost:8080 { "has_profile": true, "profile": { "uid": "uid0001", "username": "c_bata_", "icon_url": "http://example.com/hoge.png", "website_url": "http://example.com", "created_at": "1000", "updated_at": "1000" } }
実装はreflectを多用することになるので、パフォーマンスには課題があるかもしれませんが簡単にJSONのAPIに変換できた。 ただ、数値(int64)で欲しかったものがstringになってしまっていて、整数値として出力してほしい。 もしかするとバグかなと思い、ResponseWriterの代わりに次のLoggerBufferを仕込みつつ原因となっている行を探してみた。
type LoggerBuffer struct { bytes.Buffer } func (b *LoggerBuffer) Write(p []byte) (n int, err error) { log.Printf("LoggerBuffer: %s\n", p) return b.Buffer.Write(p) }
次の行が原因らしく、どうやらint64とuint64の型の場合はあえて、ダブルクオーテーションで囲っている。 こうしている理由は、JavaScriptの仕様に何か関係しているのかもしれないと思い調べてみた。
https://github.com/golang/protobuf/blob/master/jsonpb/jsonpb.go#L504-L511
EcmaScriptのNumber型
調べてみると次のStackoverflowの回答が見つかった。
Number型で表現可能なのは +/- 253 の範囲らしい。 あとbit演算は32bitの範囲で利用できる。 実際にChromeのConsoleで挙動をみてみた。
> Math.pow(2, 53) 9007199254740992 > Math.pow(2, 64) 18446744073709552000 > Math.pow(2, 53) - 1 9007199254740991 > Math.pow(2, 53) + 1 9007199254740992
値としては持つことができているが、253を超える範囲では数値演算が正しく動作しないことが確認できる。 負の数も同様。
> - Math.pow(2, 53) -9007199254740992 > - Math.pow(2, 53) -1 -9007199254740992 > - Math.pow(2, 53) + 1 -9007199254740991
そもそも何故 int64を使ったのか
GoのAPIサーバとJS間で、文字列よりも言語間で統一されてそうというイメージで時刻はUnixTimeで表現していた。
golangでは time.Time
型に Unix()
メソッドが提供されている。
このメソッドは int64 を返していたので、その値をProtocol Buffersでも使用していた。
https://godoc.org/time#Time.Unix
ただし、先程のEcmaScriptのNumber型の仕様上、JSでint64を扱うのはやめておいたほうがよさそう。 関連する問題に、「2038年問題」というのがあるみたい。
こういった問題がある以上、API上での時間の扱いとしてUnixTimeを使うのはあまりいい方法ように思えてきた。
ISO8601とRFC3339
ISO8601形式がよさそう。文字列なので先程のような問題は解決できる。 今後はこれを使おうかと思う。
- javascript - The "right" JSON date format - Stack Overflow
- ISO 8601 - Wikipedia
- RFC 3339 - Date and Time on the Internet: Timestamps
golang で time.Time
をISO8601形式に変換する
time.Now().UTC().Format(time.RFC3339)
あとは、go-sql-driver/mysqlのlocの設定もつけておいた ( ?parseTime=true&loc=Local
)。
- GitHub - go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package
- time - The Go Programming Language
おわりに
常識だったのかもしれない。この機会に知ることができてよかった。
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- 作者: Alan A.A. Donovan,Brian W. Kernighan,柴田芳樹
- 出版社/メーカー: 丸善出版
- 発売日: 2016/06/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る