circe/circe

Issue decoding BigDecimal in Scala.js

cminekime opened this issue · 3 comments

When running the following ScalaTest test in my project:

import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._

case class BigDecimalMessage(bd: BigDecimal)

"Big Decimal Test" should "Serialize and Deserialize without changing" in {
  val x = BigDecimal("98765432123456789.9876543212345678")

  val a = BigDecimalMessage(x)
  val json = a.asJson.noSpaces
  val b = decode[BigDecimalMessage](json).getOrElse(BigDecimal(0))
  println(a)
  println(json)
  println(b)
  b shouldBe a
}

It succeeds for standard Scala, but fails with the following output for Scala.js:

BigDecimalMessage(98765432123456789.9876543212345678)
{"bd":98765432123456789.9876543212345678}
BigDecimalMessage(98765432123456780)
[info] Big Decimal Test
[info] - should Serialize and Deserialize without changing *** FAILED ***
[info]   BigDecimalMessage(98765432123456780) was not equal to BigDecimalMessage(98765432123456789.9876543212345678) (FlatSpecLike.scala:1661)

I can provide more info or examples as needed. Thanks!

Unfortunately this is a known issue for Scala.js, where we can't do better than the precision of Double for numeric types (see this blog post for some detail).

These limitations on Scala.js should be documented more thoroughly—I'll take a shot at that now.

I've just added some additional documentation in #263—does that look reasonable to you, @cminekime?

The added documentation does help and thank you for the link to your blog post. It definitely helps me understand the design decisions you've made.

For what it is worth, I was able to achieve what I needed by providing my own implicit Encoder and Decoder for BigDecimals that treat them like Strings:

    implicit val bigDecimalDecoder = new Decoder[BigDecimal] {
      def apply(c: HCursor): Result[BigDecimal] =
        Xor.fromOption(c.focus.asString map { BigDecimal(_) }, DecodingFailure("BigDecimal", c.history))
    }

    implicit val bigDecimalEncoder = new Encoder[BigDecimal] {
      def apply(a: BigDecimal): Json = Json.fromString(a.toString)
    }

It may not be in the spirit of how to handle JSON numbers, but I'm glad the library was flexible enough to allow me to do this.