为什么 play-json 在 reading/parsing 时会失去精度?
Why does play-json lose precision while reading/parsing?
在下面的例子中(scala 2.11和play-json 2.13)
val j ="""{"t":2.2599999999999997868371792719699442386627197265625}"""
println((Json.parse(j) \ "t").as[BigDecimal].compare(BigDecimal("2.2599999999999997868371792719699442386627197265625")))
输出为-1
。他们不应该是平等的吗?在打印解析值时,它会打印四舍五入的值:
println((Json.parse(j) \ "t").as[BigDecimal])
给出 259999999999999786837179271969944
问题是默认情况下 play-json 配置 Jackson 解析器 MathContext
设置为 DECIMAL128
。您可以通过将 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)
...
但这超出了这个问题的范围。
老实说,我不确定为什么 MathContext
的 play-json 默认为 DECIMAL128
,但这是 play-json 维护者的问题, 也超出了这里的范围。
在下面的例子中(scala 2.11和play-json 2.13)
val j ="""{"t":2.2599999999999997868371792719699442386627197265625}"""
println((Json.parse(j) \ "t").as[BigDecimal].compare(BigDecimal("2.2599999999999997868371792719699442386627197265625")))
输出为-1
。他们不应该是平等的吗?在打印解析值时,它会打印四舍五入的值:
println((Json.parse(j) \ "t").as[BigDecimal])
给出 259999999999999786837179271969944
问题是默认情况下 play-json 配置 Jackson 解析器 MathContext
设置为 DECIMAL128
。您可以通过将 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)
...
但这超出了这个问题的范围。
老实说,我不确定为什么 MathContext
的 play-json 默认为 DECIMAL128
,但这是 play-json 维护者的问题, 也超出了这里的范围。