diff options
author | Leonid Startsev <sandwwraith@users.noreply.github.com> | 2023-09-11 17:59:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-11 17:59:29 +0200 |
commit | c2303d945bb13caf312c8fab661a9b2b359dfb9f (patch) | |
tree | 6e2a670aa673a0f3ebf0f054313fa51f2f8d378b | |
parent | 8baa04b4259274305eea6b02f5c5590b44ea126c (diff) | |
download | kotlinx.serialization-c2303d945bb13caf312c8fab661a9b2b359dfb9f.tar.gz |
Updated code examples and documentation to mention where `@Serializable` annotation can be applied directly to the types. (#2397)
Fixes #2384
-rw-r--r-- | docs/serialization-guide.md | 1 | ||||
-rw-r--r-- | docs/serializers.md | 56 | ||||
-rw-r--r-- | guide/example/example-serializer-16.kt | 9 | ||||
-rw-r--r-- | guide/example/example-serializer-17.kt | 19 | ||||
-rw-r--r-- | guide/example/example-serializer-18.kt | 34 | ||||
-rw-r--r-- | guide/example/example-serializer-19.kt | 24 | ||||
-rw-r--r-- | guide/example/example-serializer-20.kt | 15 | ||||
-rw-r--r-- | guide/example/example-serializer-21.kt | 31 | ||||
-rw-r--r-- | guide/example/example-serializer-22.kt | 20 | ||||
-rw-r--r-- | guide/example/example-serializer-23.kt | 28 | ||||
-rw-r--r-- | guide/test/SerializersTest.kt | 27 |
11 files changed, 170 insertions, 94 deletions
diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md index 445d32e3..68ede144 100644 --- a/docs/serialization-guide.md +++ b/docs/serialization-guide.md @@ -71,6 +71,7 @@ Once the project is set up, we can start serializing some classes. * <a name='serializing-3rd-party-classes'></a>[Serializing 3rd party classes](serializers.md#serializing-3rd-party-classes) * <a name='passing-a-serializer-manually'></a>[Passing a serializer manually](serializers.md#passing-a-serializer-manually) * <a name='specifying-serializer-on-a-property'></a>[Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) + * <a name='specifying-serializer-for-a-particular-type'></a>[Specifying serializer for a particular type](serializers.md#specifying-serializer-for-a-particular-type) * <a name='specifying-serializers-for-a-file'></a>[Specifying serializers for a file](serializers.md#specifying-serializers-for-a-file) * <a name='specifying-serializer-globally-using-typealias'></a>[Specifying serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias) * <a name='custom-serializers-for-a-generic-type'></a>[Custom serializers for a generic type](serializers.md#custom-serializers-for-a-generic-type) diff --git a/docs/serializers.md b/docs/serializers.md index 2da17fa4..e6bf78e3 100644 --- a/docs/serializers.md +++ b/docs/serializers.md @@ -24,6 +24,7 @@ In this chapter we'll take a look at serializers in more detail, and we'll see h * [Serializing 3rd party classes](#serializing-3rd-party-classes) * [Passing a serializer manually](#passing-a-serializer-manually) * [Specifying serializer on a property](#specifying-serializer-on-a-property) + * [Specifying serializer for a particular type](#specifying-serializer-for-a-particular-type) * [Specifying serializers for a file](#specifying-serializers-for-a-file) * [Specifying serializer globally using typealias](#specifying-serializer-globally-using-typealias) * [Custom serializers for a generic type](#custom-serializers-for-a-generic-type) @@ -713,7 +714,7 @@ because we don't control the `Date` source code. There are several ways to work ### Passing a serializer manually All `encodeToXxx` and `decodeFromXxx` functions have an overload with the first serializer parameter. -When a non-serializable class, like `Date`, is the top-level class being serialized we can use those. +When a non-serializable class, like `Date`, is the top-level class being serialized, we can use those. ```kotlin fun main() { @@ -770,6 +771,45 @@ The `stableReleaseDate` property is serialized with the serialization strategy t <!--- TEST --> +### Specifying serializer for a particular type + +[`@Serializable`][Serializable] annotation can also be applied directly to the types. +This is handy when a class that requires a custom serializer, such as `Date`, happens to be a generic type argument. +The most common use case for that is when you have a list of dates: + +<!--- INCLUDE +import java.util.Date +import java.text.SimpleDateFormat + +object DateAsLongSerializer : KSerializer<Date> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} +--> + +```kotlin +@Serializable +class ProgrammingLanguage( + val name: String, + val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> +) + +fun main() { + val df = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) + println(Json.encodeToString(data)) +} +``` + +> You can get the full code [here](../guide/example/example-serializer-16.kt). + +```text +{"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]} +``` + +<!--- TEST --> + ### Specifying serializers for a file A serializer for a specific type, like `Date`, can be specified for a whole source code file with the file-level @@ -803,7 +843,7 @@ fun main() { println(Json.encodeToString(data)) } ``` -> You can get the full code [here](../guide/example/example-serializer-16.kt). +> You can get the full code [here](../guide/example/example-serializer-17.kt). ```text {"name":"Kotlin","stableReleaseDate":1455494400000} @@ -857,7 +897,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-17.kt). +> You can get the full code [here](../guide/example/example-serializer-18.kt). ```text {"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000} @@ -905,7 +945,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-18.kt). +> You can get the full code [here](../guide/example/example-serializer-19.kt). The resulting JSON looks like the `Project` class was serialized directly. @@ -969,7 +1009,7 @@ fun main() { To actually serialize this class we must provide the corresponding context when calling the `encodeToXxx`/`decodeFromXxx` functions. Without it we'll get a "Serializer for class 'Date' is not found" exception. -> See [here](../guide/example/example-serializer-19.kt) for an example that produces that exception. +> See [here](../guide/example/example-serializer-20.kt) for an example that produces that exception. <!--- TEST LINES_START Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found. @@ -1028,7 +1068,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-20.kt). +> You can get the full code [here](../guide/example/example-serializer-21.kt). ```text {"name":"Kotlin","stableReleaseDate":1455494400000} ``` @@ -1087,7 +1127,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-21.kt). +> You can get the full code [here](../guide/example/example-serializer-22.kt). This gets all the `Project` properties serialized: @@ -1128,7 +1168,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-serializer-22.kt). +> You can get the full code [here](../guide/example/example-serializer-23.kt). The output is shown below. diff --git a/guide/example/example-serializer-16.kt b/guide/example/example-serializer-16.kt index 157208fd..3db0b7ff 100644 --- a/guide/example/example-serializer-16.kt +++ b/guide/example/example-serializer-16.kt @@ -1,4 +1,3 @@ -@file:UseSerializers(DateAsLongSerializer::class) // This file was automatically generated from serializers.md by Knit tool. Do not edit. package example.exampleSerializer16 @@ -17,9 +16,13 @@ object DateAsLongSerializer : KSerializer<Date> { } @Serializable -class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) +class ProgrammingLanguage( + val name: String, + val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date> +) fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + val df = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00"))) println(Json.encodeToString(data)) } diff --git a/guide/example/example-serializer-17.kt b/guide/example/example-serializer-17.kt index 0ac8dfe8..c5624ed3 100644 --- a/guide/example/example-serializer-17.kt +++ b/guide/example/example-serializer-17.kt @@ -1,3 +1,4 @@ +@file:UseSerializers(DateAsLongSerializer::class) // This file was automatically generated from serializers.md by Knit tool. Do not edit. package example.exampleSerializer17 @@ -10,27 +11,15 @@ import java.util.Date import java.text.SimpleDateFormat object DateAsLongSerializer : KSerializer<Date> { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsLong", PrimitiveKind.LONG) + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) } -object DateAsSimpleTextSerializer: KSerializer<Date> { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG) - private val format = SimpleDateFormat("yyyy-MM-dd") - override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value)) - override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString()) -} - -typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date - -typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date - @Serializable -class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) +class ProgrammingLanguage(val name: String, val stableReleaseDate: Date) fun main() { - val format = SimpleDateFormat("yyyy-MM-ddX") - val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) println(Json.encodeToString(data)) } diff --git a/guide/example/example-serializer-18.kt b/guide/example/example-serializer-18.kt index cca857c7..9987e822 100644 --- a/guide/example/example-serializer-18.kt +++ b/guide/example/example-serializer-18.kt @@ -6,21 +6,31 @@ import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* -@Serializable(with = BoxSerializer::class) -data class Box<T>(val contents: T) +import java.util.Date +import java.text.SimpleDateFormat + +object DateAsLongSerializer : KSerializer<Date> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsLong", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} -class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> { - override val descriptor: SerialDescriptor = dataSerializer.descriptor - override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents) - override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) +object DateAsSimpleTextSerializer: KSerializer<Date> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG) + private val format = SimpleDateFormat("yyyy-MM-dd") + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value)) + override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString()) } -@Serializable -data class Project(val name: String) +typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date + +typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date + +@Serializable +class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong) fun main() { - val box = Box(Project("kotlinx.serialization")) - val string = Json.encodeToString(box) - println(string) - println(Json.decodeFromString<Box<Project>>(string)) + val format = SimpleDateFormat("yyyy-MM-ddX") + val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00")) + println(Json.encodeToString(data)) } diff --git a/guide/example/example-serializer-19.kt b/guide/example/example-serializer-19.kt index 4d7c0729..4622665a 100644 --- a/guide/example/example-serializer-19.kt +++ b/guide/example/example-serializer-19.kt @@ -6,17 +6,21 @@ import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* -import java.util.Date -import java.text.SimpleDateFormat +@Serializable(with = BoxSerializer::class) +data class Box<T>(val contents: T) -@Serializable -class ProgrammingLanguage( - val name: String, - @Contextual - val stableReleaseDate: Date -) +class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> { + override val descriptor: SerialDescriptor = dataSerializer.descriptor + override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents) + override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder)) +} + +@Serializable +data class Project(val name: String) fun main() { - val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(Json.encodeToString(data)) + val box = Box(Project("kotlinx.serialization")) + val string = Json.encodeToString(box) + println(string) + println(Json.decodeFromString<Box<Project>>(string)) } diff --git a/guide/example/example-serializer-20.kt b/guide/example/example-serializer-20.kt index 7ce30e89..38b72e79 100644 --- a/guide/example/example-serializer-20.kt +++ b/guide/example/example-serializer-20.kt @@ -6,15 +6,8 @@ import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* -import kotlinx.serialization.modules.* import java.util.Date import java.text.SimpleDateFormat - -object DateAsLongSerializer : KSerializer<Date> { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) - override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) - override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) -} @Serializable class ProgrammingLanguage( @@ -23,13 +16,7 @@ class ProgrammingLanguage( val stableReleaseDate: Date ) -private val module = SerializersModule { - contextual(DateAsLongSerializer) -} - -val format = Json { serializersModule = module } - fun main() { val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) - println(format.encodeToString(data)) + println(Json.encodeToString(data)) } diff --git a/guide/example/example-serializer-21.kt b/guide/example/example-serializer-21.kt index f588a737..9a24b0aa 100644 --- a/guide/example/example-serializer-21.kt +++ b/guide/example/example-serializer-21.kt @@ -6,13 +6,30 @@ import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* -// NOT @Serializable -class Project(val name: String, val language: String) - -@Serializer(forClass = Project::class) -object ProjectSerializer +import kotlinx.serialization.modules.* +import java.util.Date +import java.text.SimpleDateFormat + +object DateAsLongSerializer : KSerializer<Date> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time) + override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong()) +} + +@Serializable +class ProgrammingLanguage( + val name: String, + @Contextual + val stableReleaseDate: Date +) + +private val module = SerializersModule { + contextual(DateAsLongSerializer) +} + +val format = Json { serializersModule = module } fun main() { - val data = Project("kotlinx.serialization", "Kotlin") - println(Json.encodeToString(ProjectSerializer, data)) + val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00")) + println(format.encodeToString(data)) } diff --git a/guide/example/example-serializer-22.kt b/guide/example/example-serializer-22.kt index 7f098fa7..4eba74b0 100644 --- a/guide/example/example-serializer-22.kt +++ b/guide/example/example-serializer-22.kt @@ -6,23 +6,13 @@ import kotlinx.serialization.json.* import kotlinx.serialization.encoding.* import kotlinx.serialization.descriptors.* -// NOT @Serializable, will use external serializer -class Project( - // val in a primary constructor -- serialized - val name: String -) { - var stars: Int = 0 // property with getter & setter -- serialized - - val path: String // getter only -- not serialized - get() = "kotlin/$name" - - private var locked: Boolean = false // private, not accessible -- not serialized -} - +// NOT @Serializable +class Project(val name: String, val language: String) + @Serializer(forClass = Project::class) object ProjectSerializer fun main() { - val data = Project("kotlinx.serialization").apply { stars = 9000 } - println(Json.encodeToString(ProjectSerializer, data)) + val data = Project("kotlinx.serialization", "Kotlin") + println(Json.encodeToString(ProjectSerializer, data)) } diff --git a/guide/example/example-serializer-23.kt b/guide/example/example-serializer-23.kt new file mode 100644 index 00000000..4b7de25a --- /dev/null +++ b/guide/example/example-serializer-23.kt @@ -0,0 +1,28 @@ +// This file was automatically generated from serializers.md by Knit tool. Do not edit. +package example.exampleSerializer23 + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.descriptors.* + +// NOT @Serializable, will use external serializer +class Project( + // val in a primary constructor -- serialized + val name: String +) { + var stars: Int = 0 // property with getter & setter -- serialized + + val path: String // getter only -- not serialized + get() = "kotlin/$name" + + private var locked: Boolean = false // private, not accessible -- not serialized +} + +@Serializer(forClass = Project::class) +object ProjectSerializer + +fun main() { + val data = Project("kotlinx.serialization").apply { stars = 9000 } + println(Json.encodeToString(ProjectSerializer, data)) +} diff --git a/guide/test/SerializersTest.kt b/guide/test/SerializersTest.kt index c151a150..bda3f7f4 100644 --- a/guide/test/SerializersTest.kt +++ b/guide/test/SerializersTest.kt @@ -113,50 +113,57 @@ class SerializersTest { @Test fun testExampleSerializer16() { captureOutput("ExampleSerializer16") { example.exampleSerializer16.main() }.verifyOutputLines( - "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + "{\"name\":\"Kotlin\",\"releaseDates\":[1688601600000,1682380800000,1672185600000]}" ) } @Test fun testExampleSerializer17() { captureOutput("ExampleSerializer17") { example.exampleSerializer17.main() }.verifyOutputLines( - "{\"stableReleaseDate\":\"2016-02-15\",\"lastReleaseTimestamp\":1657152000000}" + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" ) } @Test fun testExampleSerializer18() { captureOutput("ExampleSerializer18") { example.exampleSerializer18.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\"}", - "Box(contents=Project(name=kotlinx.serialization))" + "{\"stableReleaseDate\":\"2016-02-15\",\"lastReleaseTimestamp\":1657152000000}" ) } @Test fun testExampleSerializer19() { - captureOutput("ExampleSerializer19") { example.exampleSerializer19.main() }.verifyOutputLinesStart( - "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.", - "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." + captureOutput("ExampleSerializer19") { example.exampleSerializer19.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\"}", + "Box(contents=Project(name=kotlinx.serialization))" ) } @Test fun testExampleSerializer20() { - captureOutput("ExampleSerializer20") { example.exampleSerializer20.main() }.verifyOutputLines( - "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" + captureOutput("ExampleSerializer20") { example.exampleSerializer20.main() }.verifyOutputLinesStart( + "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.", + "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied." ) } @Test fun testExampleSerializer21() { captureOutput("ExampleSerializer21") { example.exampleSerializer21.main() }.verifyOutputLines( - "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}" ) } @Test fun testExampleSerializer22() { captureOutput("ExampleSerializer22") { example.exampleSerializer22.main() }.verifyOutputLines( + "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}" + ) + } + + @Test + fun testExampleSerializer23() { + captureOutput("ExampleSerializer23") { example.exampleSerializer23.main() }.verifyOutputLines( "{\"name\":\"kotlinx.serialization\",\"stars\":9000}" ) } |