10

In the following example (scala 2.11 and play-json 2.13)

val j ="""{"t":2.2599999999999997868371792719699442386627197265625}"""
println((Json.parse(j) \ "t").as[BigDecimal].compare(BigDecimal("2.2599999999999997868371792719699442386627197265625")))

The output is -1. Shouldn't they be equal ? On printing the parsed value, it prints rounded off value:

println((Json.parse(j) \ "t").as[BigDecimal]) gives 259999999999999786837179271969944

Lasf
  • 2,536
  • 1
  • 16
  • 35
advocateofnone
  • 2,527
  • 3
  • 17
  • 39

1 Answers1

9

The problem is that by default play-json configures the Jackson parser with the MathContext set to DECIMAL128. You can fix this by setting the play.json.parser.mathContext system property to unlimited. For example, in a Scala REPL that would look like this:

scala> System.setProperty("play.json.parser.mathContext", "unlimited")
res0: String = null

scala> val j ="""{"t":2.2599999999999997868371792719699442386627197265625}"""
j: String = {"t":2.2599999999999997868371792719699442386627197265625}

scala> import play.api.libs.json.Json
import play.api.libs.json.Json

scala> val res = (Json.parse(j) \ "t").as[BigDecimal]
res: BigDecimal = 2.2599999999999997868371792719699442386627197265625

scala> val expected = BigDecimal("2.2599999999999997868371792719699442386627197265625")
expected: scala.math.BigDecimal = 2.2599999999999997868371792719699442386627197265625

scala> res.compare(expected)
res1: Int = 0

Note that setProperty should happen first, before any reference to Json. In normal (non-REPL) use you'd set the property via -D on the command line or whatever.

Alternatively you could use Jawn's play-json parsing support, which just works as expected off the shelf:

scala> val j ="""{"t":2.2599999999999997868371792719699442386627197265625}"""
j: String = {"t":2.2599999999999997868371792719699442386627197265625}

scala> import org.typelevel.jawn.support.play.Parser
import org.typelevel.jawn.support.play.Parser

scala> val res = (Parser.parseFromString(j).get \ "t").as[BigDecimal]
res: BigDecimal = 2.2599999999999997868371792719699442386627197265625

Or for that matter you could switch to circe:

scala> import io.circe.Decoder, io.circe.jawn.decode
import io.circe.Decoder
import io.circe.jawn.decode

scala> decode(j)(Decoder[BigDecimal].prepare(_.downField("t")))
res0: Either[io.circe.Error,BigDecimal] = Right(2.2599999999999997868371792719699442386627197265625)

…which handles a range of number-related corner cases more responsibly than play-json in my view. For example:

scala> val big = "1e2147483648"
big: String = 1e2147483648

scala> io.circe.jawn.parse(big)
res0: Either[io.circe.ParsingFailure,io.circe.Json] = Right(1e2147483648)

scala> play.api.libs.json.Json.parse(big)
java.lang.NumberFormatException
  at java.math.BigDecimal.<init>(BigDecimal.java:491)
  at java.math.BigDecimal.<init>(BigDecimal.java:824)
  at scala.math.BigDecimal$.apply(BigDecimal.scala:287)
  at play.api.libs.json.jackson.JsValueDeserializer.parseBigDecimal(JacksonJson.scala:146)
  ...

But that's out of scope for this question.

To be honest I'm not sure why play-json defaults to DECIMAL128 for the MathContext, but that's a question for the play-json maintainers, and is also out of scope here.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • For what it's worth there's some way to do this configuration in code (not through system properties), but I don't remember it off the top of my head. – Travis Brown Mar 17 '19 at 17:03
  • Could you please see if u remember it. It would be helpful to me. Thanks – advocateofnone Mar 17 '19 at 17:20
  • @sashas I was wrong—it's only possible if you drop down to working with the `ObjectMapper` directly. See e.g. [this comment](https://github.com/playframework/play-json/blob/2.7.2/play-json/jvm/src/main/scala/play/api/libs/json/jackson/JacksonJson.scala#L30-L34). – Travis Brown Mar 17 '19 at 20:26
  • 1
    @sashas Oh, and that example code in the comment seems to be out of date, so not even that's not an option. I think you're stuck with the system property. – Travis Brown Mar 17 '19 at 20:29