Currently I'm in the need of serializing numbers with many decimal digits (30+ decimal digits to be more precise). To deserialzie a JSON file with this many decimal points I used Java's BigIntegers and BigDecimal types which works fine. The problem that arises is when I want to serialize that value. It will be serialized as an Integer or Double respectively which cuts and rounds the actual value.
One example value is this, received in a JSON file.
0.083532162010669708251953125000
After deserializing I will have exactly a value of that above as a JsonLiteral. But when I serialize this value I will get a result of:
0.08353216201066971
Which is not my desired result.
A snippet from my unit tests showing the differences between expected and actual:

My questions are now how should I serialized BigIntegers and BigDoubles and will there be support for this kind of data types in the future?
Can you, please, provide the example code you use to serialize and deserialize your data between JSON and BigIntegers?
var deserializedJson= json.decodeFromString(JsonClassSerializer, jsonToParse)
var convertedKotlin= convertJsonToKotlin(deserializedJson)
var convertedJson= convertKotlinToJson(convertedKotlin)
var serializedJson= generateJsonOutputFromKotlin(convertedJson, json)
The above sequence is what I went through. I took a JSON file as String input, configured my Json object and then decoded that input into an object instance of some JsonClass containing properties like val x: JsonArray and so on.
Then I convert that object into a different a different type that contains useable data types like for val x: List
The initial deserialization is correct, the Json -> Kotlin conversion is correct, transforming it back from Kotlin -> Json is still correct but the standard serializer when you annotate your class with @Serializable writes BigDecimal and BigIntegers as Double and Long respectively.
To clarify I did not use a custom serializer, for anything here. I simply let kotlinx handle the serialization of the intermediate type and then converted that to the actual types.
This is how I converted a JsonLiteral to BigInteger and so on:
internal fun convertJsonPrimitive(jsonPrimitive: JsonPrimitive): Any? {
return if (jsonPrimitive.isString) jsonPrimitive.contentOrNull
else jsonPrimitive.booleanOrNull
?: if (jsonPrimitive.longOrNull != null)
// if the casted value is the same as the raw json data, then the data is within double/long range
if (jsonPrimitive.longOrNull.toString() != (jsonPrimitive.content))
BigInteger(jsonPrimitive.content)
else jsonPrimitive.long
else if (jsonPrimitive.doubleOrNull != null)
if (jsonPrimitive.doubleOrNull.toString() != (jsonPrimitive.content))
BigDecimal(jsonPrimitive.content)
else jsonPrimitive.double
else
JsonNull
}
Edit: The code block seems to be displayed weirdly.
Do you have any special requirement to convert your input to JsonElement and only after that to a Kotlin class? If not, then you can skip JsonElement step and parse json to a kotlin class directly via decodeToString. You'll need to write a custom serializer for big numbers, however, it will be relatively simple: only decodeString/encodeString calls. See the sample here: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#primitive-serializer
Yes, in fact, directly converting them was my initial plan until I saw that you cannot directly serialize Any types. As you answered in some old issue that we should use JsonObject to serialize types of Map
I also fideled around with custom serializers but I gave up eventually since I couldn't get it to work. The easiest solution was for me to simply let kotlinx do the deserialization and then convert it with simple methods to standard kotlin types. Same goes for serialization.
Thanks for the answer, so I need a custom serializer for that.
Is it possible for the deserialization/serialization of BigInts/Doubles to become a feature in the future so we no longer need a custom serializer for very long numbers?
As it is, there is no way to encode a JSON number outside of Kotlin's primitive types, which means that it is impossible to encode a decimal value without inducing precision loss.
encodeString, StreamingJsonEncoder will quote the value, regardless of the descriptor kind.encodeJsonElement with JsonPrimitive, the value's string representation will be converted using String.toDoubleOrNull(), not only inducing precision loss but formatting the value using engineering notation (e.g. 1.11222333444E11).Bear in mind that ECMA-404 does not specify that JSON numbers must represent IEEE-754 values; they are simply strings of digits with optional fraction and exponent parts. From json.org:
number
integer fraction exponent
integer
digit
onenine digits
'-' digit
'-' onenine digits
digits
digit
digit digits
digit
'0'
onenine
onenine
'1' . '9'
fraction
""
'.' digits
exponent
""
'E' sign digits
'e' sign digits
sign
""
'+'
'-'
JsonEncoder should expose a mechanism to write an unquoted JSON number, which would solve this particular issue and allow for the use of non-Number types.
Most helpful comment
Yes, in fact, directly converting them was my initial plan until I saw that you cannot directly serialize Any types. As you answered in some old issue that we should use JsonObject to serialize types of Map I then did the same thing for every other type that had Any in it, i.e. JsonArray for List and so on.
I also fideled around with custom serializers but I gave up eventually since I couldn't get it to work. The easiest solution was for me to simply let kotlinx do the deserialization and then convert it with simple methods to standard kotlin types. Same goes for serialization.
Thanks for the answer, so I need a custom serializer for that.
Is it possible for the deserialization/serialization of BigInts/Doubles to become a feature in the future so we no longer need a custom serializer for very long numbers?