問題点
com.fasterxml.jackson.databind.ObjectMapperを使い、InstantやBigDecimalなどのjacksonで変換されると浮動小数点数になる値を持つObjectをデコードした際に、エンコード前の値にならない場合がある。
発生する環境
Windows環境全般
※確認できている限り、他の環境では発生しない。
原因
「値がおかしくなることがある実装例」のように、InstantObjectのようなInstantを持つObjectをデコードする際、一旦JsonNodeへの変換すると、一旦doubleに変換され、その後、Instantに変換される。
この時、windowsだとdobuleの精度がデフォルトで53bitしかないため値がおかしくなることがある。
「直接変換を行うため、値がおかしくならない実装例」のように、途中でJasonNodeへ変換せず、直接変換する場合は問題ない。
-
変換対象
public class InstantObject { // この値がおかしくなる private Instant _time; public InstantObject() { } public Instant time() { return _time; } public void time(Instant time) { this._time = time; } } -
「値がおかしくなることがある実装例」
ObjectMapper om = new ObjectMapper(); JavaTimeModule jtm = new JavaTimeModule(); om.registerModule(jtm); SimpleModule module = new SimpleModule(); // InstantObjectのデシリアライザー module.addDeserializer(InstantObject.class, new JsonDeserializer<InstantObject>() { // デコード処理 @Override public InstantObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { InstantObject ret = new InstantObject(); ObjectCodec codec = p.getCodec(); // ここで、文字列→JsonNodeに変換される際に、timeの値が一旦がdoubleになり、ここでおかしくなる場合がある。 JsonNode node = codec.readTree(p); JsonNode v = node.get("time"); System.out.println("======================="); System.out.println("is Double? : " + v.isDouble()); System.out.println("======================="); ret.setTime(p.getCodec().treeToValue(v, Instant.class)); return ret; } }); om.registerModule(module); InstantObject now = new InstantObject(); Instant targetTime = Instant.now(); now.setTime(targetTime); // エンコード:Instant→BigDecimal→文字列に変換される String encoded = om.writeValueAsString(now); try { // デコード:文字列→double→Instantに変換される InstantObject decoded = om.readValue(encoded, InstantObject.class); Instant decodedTime = decoded.getTime(); // targetTimeとdecodedTimeの値が同じにならない場合がある。 } catch (IOException e) { throw new RuntimeException("Failed to decode <" + encoded + ">.", e); }- 上記コードの実行ログ:デコードした際にDoubleになっている。
======================= is Double? : true =======================
- 上記コードの実行ログ:デコードした際にDoubleになっている。
-
「直接変換を行うため、値がおかしくならない実装例」
ObjectMapper om = new ObjectMapper(); JavaTimeModule jtm = new JavaTimeModule(); om.registerModule(jtm); Instant in = Instant.now(); // エンコード:Instant→BigDecimal→文字列に変換される String encoded = om.writeValueAsString(in); try { // デコード:文字列→Instantに変換される Instant decoded = om.readValue(encoded, Instant.class); } catch (IOException e) { throw new InternalException("Failed to decode <" + encoded + ">.", e); }
解決方法
デコードする際、途中でJasonNodeへ変換をする場合でも、浮動小数点数をDoubleNodeではなく、DecimalNodeに変換すればいいため、以下のようにUSE_BIG_DECIMAL_FOR_FLOATSを有効にする。
※この設定をすることで、文字列→BigDecimal→Instantに変換されるようになる
ObjectMapper om = new ObjectMapper();
om.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);