From 41444b129c75664bed33b13b438b208ef4cd9b38 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Thu, 5 Sep 2024 04:18:08 +0100 Subject: [PATCH] avoid reparsing numbers when serializing Update StringBasedNumericNode.scala rework Update JacksonJson.scala add containsEOrDot requested changes to containsEOrDot remove StringBasedNumericNode imports remove new function add test add int serialization test Update JsonSpec.scala Update JsonSpec.scala --- .../api/libs/json/jackson/JacksonJson.scala | 18 ++++--- .../scala/play/api/libs/json/JsonSpec.scala | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala b/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala index 44f0c090d..9c7d1edce 100644 --- a/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala +++ b/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala @@ -6,6 +6,7 @@ package play.api.libs.json.jackson import java.io.InputStream import java.io.StringWriter +import java.math.BigInteger import scala.annotation.switch import scala.annotation.tailrec @@ -26,7 +27,9 @@ import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.`type`.TypeFactory import com.fasterxml.jackson.databind.deser.Deserializers import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.databind.node.BigIntegerNode import com.fasterxml.jackson.databind.ser.Serializers +import com.fasterxml.jackson.databind.util.TokenBuffer import play.api.libs.json._ @@ -67,12 +70,8 @@ sealed class PlayJsonMapperModule(jsonConfig: JsonConfig) extends SimpleModule(" // -- Serializers. private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends JsonSerializer[JsValue] { - import java.math.BigInteger import java.math.{ BigDecimal => JBigDec } - import com.fasterxml.jackson.databind.node.BigIntegerNode - import com.fasterxml.jackson.databind.node.DecimalNode - private def stripTrailingZeros(bigDec: JBigDec): JBigDec = { val stripped = bigDec.stripTrailingZeros if (jsonConfig.bigDecimalSerializerConfig.preserveZeroDecimal && bigDec.scale > 0 && stripped.scale <= 0) { @@ -96,10 +95,15 @@ private[jackson] class JsValueSerializer(jsonConfig: JsonConfig) extends JsonSer val stripped = stripTrailingZeros(v.bigDecimal) val raw = if (shouldWritePlain) stripped.toPlainString else stripped.toString - if (raw.indexOf('E') < 0 && raw.indexOf('.') < 0) - json.writeTree(new BigIntegerNode(new BigInteger(raw))) + if (raw.exists(c => c == 'E' || c == '.')) + json.writeNumber(raw) else - json.writeTree(new DecimalNode(new JBigDec(raw))) + json match { + case _: TokenBuffer => + json.writeTree(new BigIntegerNode(new BigInteger(raw))) + case _ => + json.writeNumber(raw) + } } case JsString(v) => json.writeString(v) diff --git a/play-json/jvm/src/test/scala/play/api/libs/json/JsonSpec.scala b/play-json/jvm/src/test/scala/play/api/libs/json/JsonSpec.scala index 8411c61ea..03bf83184 100644 --- a/play-json/jvm/src/test/scala/play/api/libs/json/JsonSpec.scala +++ b/play-json/jvm/src/test/scala/play/api/libs/json/JsonSpec.scala @@ -4,6 +4,7 @@ package play.api.libs.json +import java.math.BigInteger import java.util.Calendar import java.util.Date import java.util.TimeZone @@ -477,6 +478,55 @@ class JsonSpec extends org.specs2.mutable.Specification { fromJson[JsonNode](jsNum).map(_.toString).must_==(JsSuccess("12.345")) } + "Serialize JsNumbers with integers correctly" in { + val numStrings = Seq( + "0", + "1", + "-1", + Int.MaxValue.toString, + Int.MinValue.toString, + Long.MaxValue.toString, + Long.MinValue.toString, + BigInteger.valueOf(Long.MaxValue).add(BigInteger.ONE).toString, + BigInteger.valueOf(Long.MinValue).add(BigInteger.valueOf(-1)).toString + ) + numStrings.map { numString => + val bigDec = new java.math.BigDecimal(numString) + Json.stringify(JsNumber(bigDec)).must_==(bigDec.toString) + } + } + + "Serialize JsNumbers with decimal points correctly" in { + val numStrings = Seq( + "0.123", + "1.23456789", + "-1.23456789", + Float.MaxValue.toString, + Float.MinValue.toString, + Double.MaxValue.toString, + Double.MinValue.toString, + java.math.BigDecimal.valueOf(Double.MaxValue).add(java.math.BigDecimal.valueOf(1)).toString, + java.math.BigDecimal.valueOf(Double.MinValue).add(java.math.BigDecimal.valueOf(-1)).toString + ) + numStrings.map { numString => + val bigDec = new java.math.BigDecimal(numString) + Json.stringify(JsNumber(bigDec)).must_==(bigDec.toString) + } + } + + "Serialize JsNumbers with e notation correctly" in { + val numStrings = Seq( + "1.23456789012345679012345679e999", + "-1.23456789012345679012345679e999", + "1.23456789012345679012345679e-999", + "-1.23456789012345679012345679e-999" + ) + numStrings.map { numString => + val bigDec = new java.math.BigDecimal(numString) + Json.stringify(JsNumber(bigDec)).must_==(bigDec.toString) + } + } + "parse from InputStream" in { val js = Json.obj( "key1" -> "value1",