-
Notifications
You must be signed in to change notification settings - Fork 636
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
Add kotlin.time.Instant serializers #2945
base: dev
Are you sure you want to change the base?
Changes from 2 commits
88b69d5
5b3515e
5c196df
05a1b33
b741692
89a6f95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -890,6 +890,14 @@ sealed class kotlinx.serialization.modules/SerializersModule { // kotlinx.serial | |
final fun <#A1: kotlin/Any> getContextual(kotlin.reflect/KClass<#A1>): kotlinx.serialization/KSerializer<#A1>? // kotlinx.serialization.modules/SerializersModule.getContextual|getContextual(kotlin.reflect.KClass<0:0>){0§<kotlin.Any>}[0] | ||
} | ||
|
||
final object kotlinx.serialization.builtins/InstantComponentSerializer : kotlinx.serialization/KSerializer<kotlin.time/Instant> { // kotlinx.serialization.builtins/InstantComponentSerializer|null[0] | ||
final val descriptor // kotlinx.serialization.builtins/InstantComponentSerializer.descriptor|{}descriptor[0] | ||
final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.builtins/InstantComponentSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0] | ||
|
||
final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin.time/Instant // kotlinx.serialization.builtins/InstantComponentSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0] | ||
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin.time/Instant) // kotlinx.serialization.builtins/InstantComponentSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.time.Instant){}[0] | ||
} | ||
|
||
final object kotlinx.serialization.builtins/LongAsStringSerializer : kotlinx.serialization/KSerializer<kotlin/Long> { // kotlinx.serialization.builtins/LongAsStringSerializer|null[0] | ||
final val descriptor // kotlinx.serialization.builtins/LongAsStringSerializer.descriptor|{}descriptor[0] | ||
final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.builtins/LongAsStringSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0] | ||
|
@@ -956,6 +964,14 @@ final object kotlinx.serialization.internal/FloatSerializer : kotlinx.serializat | |
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin/Float) // kotlinx.serialization.internal/FloatSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.Float){}[0] | ||
} | ||
|
||
final object kotlinx.serialization.internal/InstantSerializer : kotlinx.serialization/KSerializer<kotlin.time/Instant> { // kotlinx.serialization.internal/InstantSerializer|null[0] | ||
final val descriptor // kotlinx.serialization.internal/InstantSerializer.descriptor|{}descriptor[0] | ||
final fun <get-descriptor>(): kotlinx.serialization.descriptors/SerialDescriptor // kotlinx.serialization.internal/InstantSerializer.descriptor.<get-descriptor>|<get-descriptor>(){}[0] | ||
|
||
final fun deserialize(kotlinx.serialization.encoding/Decoder): kotlin.time/Instant // kotlinx.serialization.internal/InstantSerializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0] | ||
final fun serialize(kotlinx.serialization.encoding/Encoder, kotlin.time/Instant) // kotlinx.serialization.internal/InstantSerializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;kotlin.time.Instant){}[0] | ||
} | ||
|
||
final object kotlinx.serialization.internal/IntArraySerializer : kotlinx.serialization.internal/PrimitiveArraySerializer<kotlin/Int, kotlin/IntArray, kotlinx.serialization.internal/IntArrayBuilder>, kotlinx.serialization/KSerializer<kotlin/IntArray> // kotlinx.serialization.internal/IntArraySerializer|null[0] | ||
|
||
final object kotlinx.serialization.internal/IntSerializer : kotlinx.serialization/KSerializer<kotlin/Int> { // kotlinx.serialization.internal/IntSerializer|null[0] | ||
|
@@ -1074,6 +1090,7 @@ final val kotlinx.serialization.modules/EmptySerializersModule // kotlinx.serial | |
final fun <get-EmptySerializersModule>(): kotlinx.serialization.modules/SerializersModule // kotlinx.serialization.modules/EmptySerializersModule.<get-EmptySerializersModule>|<get-EmptySerializersModule>(){}[0] | ||
|
||
final fun (kotlin.time/Duration.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.time/Duration> // kotlinx.serialization.builtins/serializer|[email protected](){}[0] | ||
final fun (kotlin.time/Instant.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.time/Instant> // kotlinx.serialization.builtins/serializer|[email protected](){}[0] | ||
final fun (kotlin.uuid/Uuid.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin.uuid/Uuid> // kotlinx.serialization.builtins/serializer|[email protected](){}[0] | ||
final fun (kotlin/Boolean.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Boolean> // kotlinx.serialization.builtins/serializer|[email protected](){}[0] | ||
final fun (kotlin/Byte.Companion).kotlinx.serialization.builtins/serializer(): kotlinx.serialization/KSerializer<kotlin/Byte> // kotlinx.serialization.builtins/serializer|[email protected](){}[0] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright 2025-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package kotlinx.serialization.builtins | ||
|
||
import kotlinx.serialization.* | ||
import kotlinx.serialization.descriptors.* | ||
import kotlinx.serialization.encoding.* | ||
import kotlin.time.ExperimentalTime | ||
import kotlin.time.Instant | ||
|
||
@ExperimentalTime | ||
public object InstantComponentSerializer : KSerializer<Instant> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documentation is missing |
||
|
||
override val descriptor: SerialDescriptor = | ||
buildClassSerialDescriptor("kotlinx.serialization.InstantComponentSerializer") { | ||
element<Long>("epochSeconds") | ||
element<Long>("nanosecondsOfSecond", isOptional = true) | ||
} | ||
|
||
@OptIn(ExperimentalSerializationApi::class) | ||
override fun deserialize(decoder: Decoder): Instant = | ||
decoder.decodeStructure(descriptor) { | ||
var epochSeconds: Long? = null | ||
var nanosecondsOfSecond = 0 | ||
loop@ while (true) { | ||
when (val index = decodeElementIndex(descriptor)) { | ||
0 -> epochSeconds = decodeLongElement(descriptor, 0) | ||
1 -> nanosecondsOfSecond = decodeIntElement(descriptor, 1) | ||
CompositeDecoder.DECODE_DONE -> break@loop // https://youtrack.jetbrains.com/issue/KT-42262 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This ticket is said to be fixed in Kotlin 1.4.30 |
||
else -> throw SerializationException("Unexpected index: $index") | ||
} | ||
} | ||
if (epochSeconds == null) throw MissingFieldException( | ||
missingField = "epochSeconds", | ||
serialName = descriptor.serialName | ||
) | ||
Instant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond) | ||
} | ||
|
||
override fun serialize(encoder: Encoder, value: Instant) { | ||
encoder.encodeStructure(descriptor) { | ||
encodeLongElement(descriptor, 0, value.epochSeconds) | ||
if (value.nanosecondsOfSecond != 0) { | ||
encodeIntElement(descriptor, 1, value.nanosecondsOfSecond) | ||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,10 @@ import kotlinx.serialization.descriptors.* | |
import kotlinx.serialization.encoding.* | ||
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME | ||
import kotlinx.serialization.modules.* | ||
import kotlinx.serialization.test.* | ||
import kotlin.test.* | ||
import kotlin.time.Duration | ||
import kotlin.time.Instant | ||
import kotlin.time.ExperimentalTime | ||
|
||
/* | ||
* Test ensures that type that aggregate all basic (primitive/collection/maps/arrays) | ||
|
@@ -193,6 +194,27 @@ class BasicTypesSerializationTest { | |
assertEquals(Duration.parseIsoString(durationString), other) | ||
} | ||
|
||
@OptIn(ExperimentalTime::class) | ||
@Test | ||
fun testEncodeInstant() { | ||
val sb = StringBuilder() | ||
val out = KeyValueOutput(sb) | ||
|
||
val instant = Instant.parse("2020-12-09T09:16:56.000124Z") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally, having Json string tests is enough, but if you think these are necessary as well, you can keep them. |
||
out.encodeSerializableValue(Instant.serializer(), instant) | ||
|
||
assertEquals("\"${instant}\"", sb.toString()) | ||
} | ||
|
||
@OptIn(ExperimentalTime::class) | ||
@Test | ||
fun testDecodeInstant() { | ||
val instantString = "2020-12-09T09:16:56.000124Z" | ||
val inp = KeyValueInput(Parser(StringReader("\"$instantString\""))) | ||
val other = inp.decodeSerializableValue(Instant.serializer()) | ||
assertEquals(Instant.parse(instantString), other) | ||
} | ||
|
||
@Test | ||
fun testNothingSerialization() { | ||
// impossible to deserialize Nothing | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,69 @@ | ||||
/* | ||||
* Copyright 2025-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||||
*/ | ||||
|
||||
package kotlinx.serialization | ||||
|
||||
import kotlinx.serialization.* | ||||
import kotlinx.serialization.json.* | ||||
import kotlinx.serialization.builtins.* | ||||
import kotlin.time.* | ||||
import kotlin.test.* | ||||
|
||||
@OptIn(ExperimentalTime::class) | ||||
class InstantSerializationTest { | ||||
private fun iso8601Serialization(serializer: KSerializer<Instant>) { | ||||
for ((instant, json) in listOf( | ||||
Pair(Instant.fromEpochSeconds(1607505416, 124000), | ||||
"\"2020-12-09T09:16:56.000124Z\""), | ||||
Pair(Instant.fromEpochSeconds(-1607505416, -124000), | ||||
"\"1919-01-23T14:43:03.999876Z\""), | ||||
Pair(Instant.fromEpochSeconds(987654321, 123456789), | ||||
"\"2001-04-19T04:25:21.123456789Z\""), | ||||
Pair(Instant.fromEpochSeconds(987654321, 0), | ||||
"\"2001-04-19T04:25:21Z\""), | ||||
)) { | ||||
assertEquals(json, Json.encodeToString(serializer, instant)) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use kotlinx.serialization/formats/json-tests/commonTest/src/kotlinx/serialization/features/UuidTest.kt Line 18 in fa797bc
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would check that all Json flavors (string, inputStream, JsonElement) behave identically |
||||
assertEquals(instant, Json.decodeFromString(serializer, json)) | ||||
} | ||||
} | ||||
|
||||
private fun componentSerialization(serializer: KSerializer<Instant>) { | ||||
for ((instant, json) in listOf( | ||||
Pair(Instant.fromEpochSeconds(1607505416, 124000), | ||||
"{\"epochSeconds\":1607505416,\"nanosecondsOfSecond\":124000}"), | ||||
Pair(Instant.fromEpochSeconds(-1607505416, -124000), | ||||
"{\"epochSeconds\":-1607505417,\"nanosecondsOfSecond\":999876000}"), | ||||
Pair(Instant.fromEpochSeconds(987654321, 123456789), | ||||
"{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":123456789}"), | ||||
Pair(Instant.fromEpochSeconds(987654321, 0), | ||||
"{\"epochSeconds\":987654321}"), | ||||
)) { | ||||
assertEquals(json, Json.encodeToString(serializer, instant)) | ||||
assertEquals(instant, Json.decodeFromString(serializer, json)) | ||||
} | ||||
// check that having a `"nanosecondsOfSecond": 0` field doesn't break deserialization | ||||
assertEquals(Instant.fromEpochSeconds(987654321, 0), | ||||
Json.decodeFromString(serializer, | ||||
"{\"epochSeconds\":987654321,\"nanosecondsOfSecond\":0}")) | ||||
// "epochSeconds" should always be present | ||||
assertFailsWith<SerializationException> { Json.decodeFromString(serializer, "{}") } | ||||
assertFailsWith<SerializationException> { Json.decodeFromString(serializer, "{\"nanosecondsOfSecond\":3}") } | ||||
} | ||||
|
||||
@Test | ||||
fun testIso8601Serialization() { | ||||
iso8601Serialization(Instant.serializer()) | ||||
} | ||||
|
||||
@Test | ||||
fun testComponentSerialization() { | ||||
componentSerialization(InstantComponentSerializer) | ||||
} | ||||
|
||||
@Test | ||||
fun testDefaultSerializers() { | ||||
// should be the same as the ISO 8601 | ||||
iso8601Serialization(Json.serializersModule.serializer()) | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
[versions] | ||
kotlin = "2.1.0" | ||
kotlin = "2.1.20-RC" | ||
kover = "0.8.2" | ||
dokka = "2.0.0-Beta" | ||
knit = "0.5.0" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think hardly anyone remembers what exactly is written in section 5.4.2.1b of a paid document