I'm using Kotlin/JVM 1.8.0 and Kotlinx Serialization 1.4.1.
I need to encode a java.math.BigDecimal and java.math.BigInteger to JSON.
I'm using BigDecimal and BigInteger because the values I want to encode can be larger than a Double can hold, and also I want to avoid errors with floating-point precision. I don't want to encode the numbers as strings because JSON is read by other programs, so it needs to be correct.
The JSON spec places no restriction on the length of numbers, so it should be possible.
When I try and use BigDecimal and BigInteger directly, I get an error
import java.math.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class FooNumbers(
  val decimal: BigDecimal,
  val integer: BigInteger,
)
Serializer has not been found for type 'BigDecimal'. To use context serializer as fallback, explicitly annotate type or property with @Contextual
Serializer has not been found for type 'BigInteger'. To use context serializer as fallback, explicitly annotate type or property with @Contextual
I tried creating custom serializers for BigDecimal and BigInteger (and typealiases for convenience), but because these use toDouble() and toLong() they lose precision!
typealias BigDecimalJson = @Serializable(with = BigDecimalSerializer::class) BigDecimal
private object BigDecimalSerializer : KSerializer<BigDecimal> {
  override val descriptor = PrimitiveSerialDescriptor("java.math.BigDecimal", PrimitiveKind.DOUBLE)
  override fun deserialize(decoder: Decoder): BigDecimal =
    decoder.decodeDouble().toBigDecimal()
  override fun serialize(encoder: Encoder, value: BigDecimal) =
    encoder.encodeDouble(value.toDouble())
}
typealias BigIntegerJson = @Serializable(with = BigIntegerSerializer::class) BigInteger
private object BigIntegerSerializer : KSerializer<BigInteger> {
  override val descriptor = PrimitiveSerialDescriptor("java.math.BigInteger", PrimitiveKind.LONG)
  override fun deserialize(decoder: Decoder): BigInteger =
    decoder.decodeLong().toBigInteger()
  override fun serialize(encoder: Encoder, value: BigInteger) =
    encoder.encodeLong(value.toLong())
}
When I encode and decode an example instance, a different result is returned.
import java.math.*
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
@Serializable
data class FooNumbers(
  val decimal: BigDecimalJson,
  val integer: BigIntegerJson,
)
fun main() {
  val fooDecimal = BigDecimal("0.1234567890123456789012345678901234567890")
  val fooInteger = BigInteger("9876543210987654321098765432109876543210")
  val fooNumbers = FooNumbers(fooDecimal, fooInteger)
  println("$fooNumbers")
  val encodedNumbers = Json.encodeToString(fooNumbers)
  println(encodedNumbers)
  val decodedFooNumbers = Json.decodeFromString<FooNumbers>(encodedNumbers)
  println("$decodedFooNumbers")
  require(decodedFooNumbers == fooNumbers)
}
The require(...) fails:
FooNumbers(decimal=0.1234567890123456789012345678901234567890, integer=9876543210987654321098765432109876543210)
{"decimal":0.12345678901234568,"integer":1086983617567424234}
FooNumbers(decimal=0.12345678901234568, integer=1086983617567424234)
Exception in thread "main" java.lang.IllegalArgumentException: Failed requirement.
    at MainKt.main(asd.kt:32)
    at MainKt.main(asd.kt)
