CONTENTS
ENVIRONMENTS
- TypeScript : 4.0.2
- protobufjs : 6.10.1
なぜそんなことをするのか
- API との通信で使用したい
- Local Storage への保存で使用したい
API や永続化層とのやり取りで protocol buffers を使用したい。 単に JSON を使用する場合、フィールド名の変更がやりにくい。 protocol buffers を使用すれば、フィールド名の変更はやり放題。 最近 DDD で書こうとしている関係で名前の変更しやすさは重要。
protobufjs のインストール
javascript から protocol buffers するには protobufjs パッケージを使用する。
npm install --save-dev protobufjs
これでコードを生成するコマンドが使用可能になる。
proto ファイル定義
以下の内容で proto ファイルを定義しておく。
// hello_world.proto syntax = "proto3"; package hello_world; message Hello { string world = 1; int32 code = 2; } message Greet { string hello = 1; int32 number = 2; }
コード生成
以下のコードで js ファイルを生成できる。
pbjs -t static-module -w es6 -o hello_pb.js hello_world.proto
このコマンドには proto ファイルは複数指定できる。 いくつも proto ファイルがある場合は1つにまとめることもできるし、そうしなくてもいい。
さらに、TypeScript 用の型定義ファイルを以下のように生成できる。
pbts -o hello_pb.d.ts hello_pb.js
エンコード
データのエンコードは以下の通り。
import { hello_world } from "hello_pb.js"; const hello = new hello_world.Hello(); hello.world = "protobufjs"; hello.code = 1; const data = hello_world.Hello.encode(hello).finish(); console.log(btoa(String.fromCharCode.apply(null, Array.from(data))));
finish()
しないと Uint8Array にしてくれない。
Uint8Array から保存用に base64 するため、String.fromCharCode
でバイト列を文字列にした後 btoa している。
encode メソッドには Plain Javascript Object も指定できる。 しかし、せっかく TypeScript でやっているので型チェックが効くようにメッセージオブジェクトを使用したい。
デコード
データのデコードは以下の通り。
import { hello_world } from "hello_pb.js"; const message = "Cgpwcm90b2J1ZmpzEAE="; // 先のエンコードの出力結果 try { console.log(hello_world.Hello.decode(Uint8Array.from(atob(message), c => c.charCodeAt(0)))); } catch (err) { console.log(`decode error!: ${err}`); }
デコードエラーで例外が出るので try-catch する必要がある。
proto 定義でフィールド名を変えてみても、ちゃんとデコードできる。 感動。
ちなみに先の proto 定義で Hello のほかに Greet というメッセージも定義しておいた。 Hello でエンコードした内容を Greet でデコードしてみると、無事(涙)デコードできる。 どの型でエンコードしてどの型でデコードするのか、そこをちゃんとするのはプログラムを書く人の責任なんだな。
まとめ
TypeScript で protocol buffers のエンコード・デコード方法をまとめた。 とりあえずこれで Local Storage へ保存するときに protocol buffers を使用できる。