Skip to content

Commit

Permalink
avoid reparsing numbers when serializing
Browse files Browse the repository at this point in the history
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
  • Loading branch information
pjfanning committed Sep 15, 2024
1 parent 6d2feae commit 41444b1
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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._

Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand Down
50 changes: 50 additions & 0 deletions play-json/jvm/src/test/scala/play/api/libs/json/JsonSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 41444b1

Please sign in to comment.