大きな数値をJSONとして返す時に注意すること(Spring Bootでの実装例)

2016/07/29

検証version: Spring Boot 1.3.5

結論を一文で初めにいうと、Spring BootではJSONで返したいJavaBeansのfieldに必要に応じて @JsonSerialize(using = ToStringSerializer.class) を付けようということ。
以下で理由や現象を、Spring Bootで実装する場合の方法を知ることを目標として、説明する。

目次

JavaScriptの数値についての知識
JSONを返すRest APIの例
JSONを返すRest APIの例の解決策

JavaScriptの数値についての知識

JavaScriptで扱える数値は2の53乗 - 1までである。
JavaScriptの一部をベースに作られているJSONも同様に、2^53(9007199254740992)未満の数しか扱えない。
2^53と2^54は、たまたま計算がうまくいくが、2^55からは下位桁が0に丸められてしまい、2^70からは指数表示されてしまう。

JavaScriptでは,内部的に数値を「IEEE754」という規格に従って「64ビット倍精度」で保管している。

この規格で処理・表現できる最大値が,2の53乗 - 1なのだ。
最大値をオーバーした瞬間,正確さは保証されなくなる。

一番上の桁の正確さ(=有効数字)を保とうとする結果,一番下の桁から正確さが失われていく。

※参考および引用:http://language-and-engineering.hatenablog.jp/entry/20150513/JavaScriptIeee754OutOfRangeError

Number.prototype.toFixed([digits]) メソッドを使用すれば正確な文字列表記が取得できるが、サーバとブラウザ間で気軽にJSONを扱いたいのに毎回.toFixed()を呼ぶのは大変である。
※参考:MSD:Number.prototype.toFixed()

JSONを返すRest APIの例

Rest APIとして、桁数の多い数値型のIDをJSONで返すようなコードを書くとき、DB上でNumber型等になっていれば、Java上ではBigIntegerやBigDecimalでもつだろう。

例えば、ビル名からビル情報を曖昧検索してJSONで返す検索機能を次のようなJavaBeansとRestControllerで実装するとする。
JavaBeansはDBの検索結果を受け取る用途とJSONに変換される用途を兼用している。

リクエストを受けるとサーバのJavaの世界では次のようなJSONが生成される。

ブラウザが受け取るResponse Bodyも上と同じものになる。
しかし、受け取ったJSONをJavaScriptがパースした時点で、8144587379213241111は8144587379213240000となってしまう。

JSONを返すRest APIの例の解決策

解決策は、DBやJavaの世界では数値型で保持するも、JSONとして応答するときには文字列に変換すること。

Springの@RequestMappingのproducesでJSONを指定し、JavaのオブジェクトをJSONに自動で変換してレスポンスを返す場合、内部ではJacksonライブラリが使われている。
Jacksonには@JsonSerializeというアノテーションがあり、getterやfieldに付けることでJSONにするときの値の変換ロジックを与えることができる。
独自に作った変換ロジックを持つクラスを指定してもいいのだが、今回のBigIntegerをStringにしたいというようなよくある例であれば既にJacksonが用意したクラス(ToStringSerializer.class)がある。

アノテーションを付けたBuildingクラス

MasterInfoControllerクラスには変更は一切入らない。

この対応をとるとobjectIdが文字列としてJSONに変換され、Javaがレスポンスを返す。

JavaScriptがこれをパースしても、文字列なので8144587379213241111が8144587379213240000に変わることはない。

-Java, JavaScript
-,