Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Number #170

Closed
tadfisher opened this issue Apr 1, 2021 · 9 comments
Closed

Implement Number #170

tadfisher opened this issue Apr 1, 2021 · 9 comments
Assignees
Labels
enhancement New feature or request

Comments

@tadfisher
Copy link

tadfisher commented Apr 1, 2021

Is your feature request related to a problem? Please describe.

I'm trying to serialize a BigDecimal using kotlinx.serialization into a JSON-formatted string, but the library gives me two choices:

  1. Convert to a Double before serializing, resulting in IEEE-754-induced precision loss
  2. Serialize as a quoted string, breaking compatibility with the receiving deserializer

Describe the solution you'd like

kotlinx.serialization does support the kotlin.Number type, which does exactly what I want: call toString() on the value and encode the resulting string as an unquoted JSON number.

Describe alternatives you've considered

The other option for me is to add expect class BigDecimal : Number and essentially reimplement this library.

Additional context

Bug report about serializing bignums: Kotlin/kotlinx.serialization#1051

@tadfisher
Copy link
Author

tadfisher commented Apr 1, 2021

Here's a hack I found to make this work:

// HACK: Wrap BigDecimal to extend Number, allowing us to serialize as an unquoted string.
private class BigDecimalNumber(val value: BigDecimal) : Number() {
    override fun toByte(): Byte = throw UnsupportedOperationException()
    override fun toChar(): Char = throw UnsupportedOperationException()
    override fun toDouble(): Double = throw UnsupportedOperationException()
    override fun toFloat(): Float = throw UnsupportedOperationException()
    override fun toInt(): Int = throw UnsupportedOperationException()
    override fun toLong(): Long = throw UnsupportedOperationException()
    override fun toShort(): Short = throw UnsupportedOperationException()
    override fun toString(): String = value.toStringExpanded()
}

@Serializer(BigDecimal::class)
object BigDecimalSerializer : KSerializer<BigDecimal> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.DOUBLE)

    override fun deserialize(decoder: Decoder): BigDecimal {
        val element = (decoder as JsonDecoder).decodeJsonElement() as JsonPrimitive
        return element.content.toBigDecimal()
    }

    override fun serialize(encoder: Encoder, value: BigDecimal) {
        val element = JsonPrimitive(BigDecimalNumber(value))
        (encoder as JsonEncoder).encodeJsonElement(element)
    }
}

Note: This hack does not avoid precision loss from conversion to Double, as JsonSerializer converts the string representation to a Double internally.

@ionspin
Copy link
Owner

ionspin commented Apr 1, 2021

Hi @tadfisher ,

I actually wanted to implement serialization long time ago, it just slipped my mind. Thanks for the feature request and the workaround, I'll see if I can write a proper solution over the weekend!

@ionspin ionspin self-assigned this Apr 1, 2021
@ionspin ionspin added the enhancement New feature or request label Apr 1, 2021
@ionspin
Copy link
Owner

ionspin commented May 10, 2021

After thinking about this for a bit I decided not to do it, because I don't want to have any additional dependencies except Kotlin standard library, at least for now.

@volkert-fastned
Copy link

volkert-fastned commented Feb 26, 2024

@tadfisher Thanks for the workaround! I believe the precision loss on serialization can be avoided when we make use of the (currently still experimental) JsonUnquotedLiteral instead of JsonPrimitive, which would also eliminate the need for the BigDecimalNumber wrapper. This is done by rewriting the override fun serialize part as follows:

    override fun serialize(encoder: Encoder, value: BigDecimal) {
        val element = JsonUnquotedLiteral(value.toStringExpanded())
        (encoder as JsonEncoder).encodeJsonElement(element)
    }

@ionspin I'm not sure what "additional dependencies" you are referring to. With the above modification, @tadfisher's workaround only needs the following mult-platform imports:

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.toBigDecimal
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.JsonUnquotedLiteral

@ionspin
Copy link
Owner

ionspin commented Feb 26, 2024

I am referring to kotlinx.serialization library.

@volkert-fastned
Copy link

@ionspin OK, but then it woul be useful to include this serializer in the bignum-serialization-kotlinx library, right? that library/dependency is already dependent on kotlinx.serialization.

@ionspin
Copy link
Owner

ionspin commented Mar 1, 2024

Yes, that would be great, pull request would be highly appreciated!

@volkert-fastned
Copy link

Would be happy to. But bignum-serialization-kotlinx doesn't appear to have its own repository. Is that correct?

@ionspin
Copy link
Owner

ionspin commented Mar 1, 2024

Yeah, that is correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants