为什么play-json在读取/解析时会失去精度?

时间:2019-03-17 14:36:39

标签: scala playframework play-json

在以下示例中(scala 2.11和play-json 2.13)

A = {1 5 10 15 20}
B = {2 3 4 6 7}
C = {25 30 35 40 45}
D = {8 9 90 100 145}

median(A,B,C,D) = 10

输出为val j ="""{"t":2.2599999999999997868371792719699442386627197265625}""" println((Json.parse(j) \ "t").as[BigDecimal].compare(BigDecimal("2.2599999999999997868371792719699442386627197265625"))) 。他们不应该平等吗?在打印解析后的值时,它会打印四舍五入的值:

-1给出println((Json.parse(j) \ "t").as[BigDecimal])

1 个答案:

答案 0 :(得分:8)

问题在于,默认情况下,play-json将MathContext设置为DECIMAL128来配置Jackson解析器。您可以通过将play.json.parser.mathContext系统属性设置为unlimited来解决此问题。例如,在Scala REPL中如下所示:

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

请注意,setProperty应该先发生,然后再引用Json。在正常(非REPL)使用中,您可以通过命令行上的-D或其他方式设置属性。

或者,您可以使用Jawn的play-json解析支持,该支持可立即使用:

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

或者为此,您可以切换到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)

…在我看来,它比play-json更负责任地处理了一系列与数字相关的极端情况。例如:

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)
  ...

但是这个问题超出了范围。

说实话,我不确定为什么DECIMAL128的play-json默认为MathContext,但这是play-json维护者的一个问题,在这里也不在讨论之内。 / p>