aSe*_*emy 6 serialization json bigdecimal kotlin kotlinx.serialization
我正在使用 Kotlin/JVM 1.8.0 和 Kotlinx 序列化 1.4.1。
我需要将 ajava.math.BigDecimal和编码java.math.BigInteger为 JSON。
我正在使用BigDecimaland ,BigInteger因为我想要编码的值可能大于 aDouble可以容纳的值,而且我还想避免浮点精度的错误。我不想将数字编码为字符串,因为 JSON 是由其他程序读取的,因此它需要正确。
JSON 规范对数字的长度没有限制,所以应该是可能的。
当我尝试直接使用BigDecimaland时BigInteger,出现错误
import java.math.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class FooNumbers(
val decimal: BigDecimal,
val integer: BigInteger,
)
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
BigDecimal我尝试为and BigInteger(和typealiases 为了方便起见)创建自定义序列化器,但是因为这些使用toDouble()并且toLong()它们失去了精度!
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())
}
Run Code Online (Sandbox Code Playgroud)
当我对示例实例进行编码和解码时,会返回不同的结果。
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)
}
Run Code Online (Sandbox Code Playgroud)
失败require(...):
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)
Run Code Online (Sandbox Code Playgroud)
Kotlinx Serialization 1.5.0中可以对原始 JSON 进行编码,该版本于 2023 年 2 月 24 日发布,并且处于实验阶段。在早期版本中这是不可能的。
tl:dr:跳至此答案底部的“完整示例”
JsonDecoder请注意,只有编码需要解决方法 - 解码,BigDecimal并且BigInteger只要JsonDecoder使用即可直接工作!
private object BigDecimalSerializer : KSerializer<BigDecimal> {
// ...
override fun deserialize(decoder: Decoder): BigDecimal =
when (decoder) {
// must use decodeJsonElement() to get the value, and then convert it to a BigDecimal
is JsonDecoder -> decoder.decodeJsonElement().jsonPrimitive.content.toBigDecimal()
else -> decoder.decodeString().toBigDecimal()
}
}
Run Code Online (Sandbox Code Playgroud)
JsonUnquotedLiteralJsonUnquotedLiteral()为了进行编码,在编码 JSON 时必须使用新函数。
private object BigDecimalSerializer : KSerializer<BigDecimal> {
// ...
override fun serialize(encoder: Encoder, value: BigDecimal) =
when (encoder) {
// use JsonUnquotedLiteral() to encode the BigDecimal literally
is JsonEncoder -> encoder.encodeJsonElement(JsonUnquotedLiteral(value.toPlainString()))
else -> encoder.encodeString(value.toPlainString())
}
}
Run Code Online (Sandbox Code Playgroud)
Kotlinx Serialization 用于typealias定义全局可用的序列化策略。让我们做同样的事情BigDecimal
typealias BigDecimalJson = @Serializable(with = BigDecimalSerializer::class) BigDecimal
Run Code Online (Sandbox Code Playgroud)
创建序列化器后,typealias可以使用 esFooNumber自动使用KSerializers.
@Serializable
data class FooNumbers(
val decimal: BigDecimalJson,
val integer: BigIntegerJson,
)
Run Code Online (Sandbox Code Playgroud)
实际的主要功能没有改变——和以前一样。
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)
}
Run Code Online (Sandbox Code Playgroud)
现在BigDecimal和BigInteger可以被精确地编码和解码,不会损失精度!
FooNumbers(decimal=0.1234567890123456789012345678901234567890, integer=9876543210987654321098765432109876543210)
{"decimal":0.1234567890123456789012345678901234567890,"integer":9876543210987654321098765432109876543210}
FooNumbers(decimal=0.1234567890123456789012345678901234567890, integer=9876543210987654321098765432109876543210)
Run Code Online (Sandbox Code Playgroud)
这是完整的代码:
import java.math.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.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)
}
typealias BigDecimalJson = @Serializable(with = BigDecimalSerializer::class) BigDecimal
@OptIn(ExperimentalSerializationApi::class)
private object BigDecimalSerializer : KSerializer<BigDecimal> {
override val descriptor = PrimitiveSerialDescriptor("java.math.BigDecimal", PrimitiveKind.DOUBLE)
/**
* If decoding JSON uses [JsonDecoder.decodeJsonElement] to get the raw content,
* otherwise decodes using [Decoder.decodeString].
*/
override fun deserialize(decoder: Decoder): BigDecimal =
when (decoder) {
is JsonDecoder -> decoder.decodeJsonElement().jsonPrimitive.content.toBigDecimal()
else -> decoder.decodeString().toBigDecimal()
}
/**
* If encoding JSON uses [JsonUnquotedLiteral] to encode the exact [BigDecimal] value.
*
* Otherwise, [value] is encoded using encodes using [Encoder.encodeString].
*/
override fun serialize(encoder: Encoder, value: BigDecimal) =
when (encoder) {
is JsonEncoder -> encoder.encodeJsonElement(JsonUnquotedLiteral(value.toPlainString()))
else -> encoder.encodeString(value.toPlainString())
}
}
typealias BigIntegerJson = @Serializable(with = BigIntegerSerializer::class) BigInteger
@OptIn(ExperimentalSerializationApi::class)
private object BigIntegerSerializer : KSerializer<BigInteger> {
override val descriptor = PrimitiveSerialDescriptor("java.math.BigInteger", PrimitiveKind.LONG)
/**
* If decoding JSON uses [JsonDecoder.decodeJsonElement] to get the raw content,
* otherwise decodes using [Decoder.decodeString].
*/
override fun deserialize(decoder: Decoder): BigInteger =
when (decoder) {
is JsonDecoder -> decoder.decodeJsonElement().jsonPrimitive.content.toBigInteger()
else -> decoder.decodeString().toBigInteger()
}
/**
* If encoding JSON uses [JsonUnquotedLiteral] to encode the exact [BigInteger] value.
*
* Otherwise, [value] is encoded using encodes using [Encoder.encodeString].
*/
override fun serialize(encoder: Encoder, value: BigInteger) =
when (encoder) {
is JsonEncoder -> encoder.encodeJsonElement(JsonUnquotedLiteral(value.toString()))
else -> encoder.encodeString(value.toString())
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1902 次 |
| 最近记录: |