気の利くJSON.stringify

株式会社シンメトリック新入社員の青木です。研修がもうすぐ終わりそうでどきどきしてます。

研修ブログ ゼロからの通信対戦オセロ

今週の目標

通信対戦のできるオセロゲームを作るという研修ですが、この作ったゲームは最終的に会社内で開催されるオセロ大会で用いられることが決まっています。

触れ込みは大会とのことですが、大会とはどのようなことができれば大会になるのかの考えに今まで及んでいませんでした。また、大会をするために、例えばトーナメントを自動で組んでマッチングさせるような機能、などをつけなさいといった指示もありませんでした。一応ランキングとして勝率順に並べて表示する機能は作れてはいます。

今もなおこの部分の具体的な指示がないので、どのような形式でも一応柔軟に対応できるようにしておこうとは考え、チャット機能を付けることにしました。参加者の方々が誰と対戦するかの順番等を文字で話し合える場を設ければ、どのような形式の大会でもある程度以上には対応できるだろうと考えたからです。

もともと、websocket通信するための機能を作るためにチャットアプリケーションのソースコードを参考にしていたので、チャット機能を追加は簡単だということも要因としてありました。

ここで、簡単ではあると考えていたのですが、問題があることに気づきました。今までのwebsocket通信で送られるサーバーとブラウザ間でやり取りされるデータはカンマ区切りのテキストを特に変換通していないものでしたので、チャットにて自由に送信される文にカンマが混ざると不具合が生じます。

おそらくカンマ区切りのCSVのデータという部分を変えずとも、カンマの入力に対して不具合を避ける方法はいくつかあるとは思うのですが、そもそもいまだにカンマ区切りという手法を使っているのが主流ではないことが想像つくので、形式を変えることにしました。

調べたところ、オブジェクトを変換することで利用できるJSON形式というのがあるらしいので、今回はこの使用に関して書いていきます。

関連記事 パス式とJSON:データの一部にアクセスする話

行ったアプローチ

まず先にブラウザでのjavascriptでどう扱ってみたかということを書きます。

javascriptにおけるオブジェクトからJSON文字列を作る際には以下のように書きます。


var obj = new Object();
obj.a = ~
obj.b = ~
obj.c = ~
//好きな要素を入れる

var JSONString = JSON.stringify(obj);

これだけでJSONStringに {“a”:”~”, “b”:”~”, “c”:”~”, ……} といった文字列が格納されます。簡単でした……。

JSON.stringify()は自動でエスケープ文字(「”」であれば「\”」)に置き換えてくれるので、これでチャット入力時にカンマやダブルコーテーションといったJSONの書式中にも使われているものを混ぜても不具合が生じなさそうです。

逆にサーバーから受け取ったJSON文字列をオブジェクトや配列にする変換するには次のものを使います。


var jsonString  = ‘{“a”:”~”, “b”:”~”, “c”:”~”, ……}’
var obj = JSON.parse(jsonString);

ブラウザ自体がこのJSON.stringifyとJSON.parseのふたつの機能をサポートしているのであれば、このふたつにすべて任せれば苦労なく扱うことができます。ですので簡単にカンマ区切りからの切り替えができました。

次にサーバーに配備しているJAVAでのJSON文字列の扱いです。

JSON文字列を取り扱うための手法として、今回はJacsksonを用いました。 jarファイルのダウンロードページはこちらです。 http://wiki.fasterxml.com/JacksonDownload

JAVAはjavascriptよりデータの型が厳格なので、変換する際の型を指定するクラスを別個に記述する必要がありました。

以下のクラスは実際にオセロゲームに使っているものの一部を抜き出して載せています。


public class Message {
    public int type;
    public String name;
    public List<String[]> process;
    public List<Map.Entry<String, Integer>> rank;
    public Map<String, Integer> active;
}

先にこのように型の指定だけのクラスをつくっておきました。

オブジェクトからJSON文字列を生成する際には以下のように記述します。


Message obj = new Message();	//型の指定がしてあるクラスからオブジェクトを生成する。
ObjectMapper mapper1 = new ObjectMapper();
obj.type = ~;
obj.name = “~”;
//好きな要素を入れる
String JSONString = mappersend.writeValueAsString(obj);

これで指定の順どおりのJSON文字列を生成できます。

そしてこの逆の、JSON文字列をオブジェクトを生成するためには以下です。


String JSONString = {“type”:~, “name”:”~”, ……};	//受け取ったJSON文字列
ObjectMapper mapper2 = new ObjectMapper();
Message obj = mapper2.readValue(JSONString, Message.class);	//JSONStringが指定の型で変換されたobjを生成

これで受け取ったJSON文字列の値の部分がobj.typeとかobj.nameのとおりに分かれて格納されます。

今回はJacksonを用いましたが、こうしてみるとJAVAでもJSON文字列の取り扱いは簡単そうです。

作業結果

ここまでいかにも簡単に変換してみたふうに書いてみました。ただ、不具合が起こるかというとそうではないので気にする必要はないとはいえ、この状態だと一点気に入らない部分があるのでここでそれに触れます。

javascriptでJSON文字列を生成したときとJAVAでJacksonを用いてJSON文字列を生成したときの形が異なることが気に入りませんでした。 というのは、javascriptにおいてはオブジェクトの値がundefinedあるいは空文字列であった場合、JSON文字列に変換されたときにそれらが反映されません。一方JAVAにおいては、オブジェクトを生成し、一部だけに値を格納して他は触らずにJSON文字列に変換した場合、格納していないのに値をnullとして反映した文字列になります。送る必要がないので格納しなかったというのにこれでは長くなってしまうので気に入りませんでした。

javascriptでのJSON.stringifyは値がundefinedあるいは空文字であった場合に省略してくれる仕様だったので何も考えずともすっきりとした文字列が生成されてくれたのですが、JAVAではなんだか長ったらしいです。

まあそもそもjavascriptではあらかじめキーの部分を宣言する必要がなく、JAVAでは型の指定をするために未初期化でも宣言する必要はあり、そして未初期化のままならnullになるので仕方がないとはいえるのですが、気に入らないのでこのnullを消します。

といっても型の指定のためのクラスのすぐ上にこのアノテーションを付けるだけでした。


@JsonInclude(JsonInclude.Include.NON_EMPTY)

これはJacksonのバージョンが2.xの場合であり、バージョンが1.xの場合は別の書き方になるので注意してください。

JSON導入のきっかけとしてチャットで文字制限させたくないというのがありましたが、このようにJSON.stringifyがとても気が利くので改修した結果チャット機能を付けることができました。

課題

前回の更新において、最後に課題として見た目が悪いからCSSを弄る、として今回のブログ更新のためのネタにしようとしていたのですが、以上のように別作業をしていたのでまだ見た目の洗練がなされていません。実際に現在CSSでの作業をしており、なかなか思い通りにいかない面もあり、期限的にももう近いのですが、最後に向けて仕上げていきます。