Skip to content

Commit 41b3996

Browse files
authored
Add some documentation for ProtoBufSchemaGenerator in formats.md (Kotlin#1855)
1 parent ce366de commit 41b3996

13 files changed

+858
-201
lines changed

docs/formats.md

+69-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ stable, these are currently experimental features of Kotlin Serialization.
1717
* [Field numbers](#field-numbers)
1818
* [Integer types](#integer-types)
1919
* [Lists as repeated fields](#lists-as-repeated-fields)
20+
* [ProtoBuf schema generator (experimental)](#protobuf-schema-generator-experimental)
2021
* [Properties (experimental)](#properties-experimental)
2122
* [Custom formats (experimental)](#custom-formats-experimental)
2223
* [Basic encoder](#basic-encoder)
@@ -428,6 +429,62 @@ Field #1: 08 Varint Value = 2, Hex = 02
428429
Field #1: 08 Varint Value = 3, Hex = 03
429430
```
430431

432+
### ProtoBuf schema generator (experimental)
433+
434+
As mentioned above, when working with protocol buffers you usually use a ".proto" file and a code generator for your
435+
language. This includes the code to serialize your message to an output stream and deserialize it from an input stream.
436+
When using Kotlin Serialization this step is not necessary because your `@Serializable` Kotlin data types are used as the
437+
source for the schema.
438+
439+
This is very convenient for Kotlin-to-Kotlin communication, but makes interoperability between languages complicated.
440+
Fortunately, you can use the ProtoBuf schema generator to output the ".proto" representation of your messages. You can
441+
keep your Kotlin classes as a source of truth and use traditional protoc compilers for other languages at the same time.
442+
443+
As an example, we can display the following data class's ".proto" schema as follows.
444+
445+
<!--- INCLUDE
446+
import kotlinx.serialization.*
447+
import kotlinx.serialization.protobuf.*
448+
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
449+
-->
450+
451+
```kotlin
452+
@Serializable
453+
data class SampleData(
454+
val amount: Long,
455+
val description: String?,
456+
val department: String = "QA"
457+
)
458+
fun main() {
459+
val descriptors = listOf(SampleData.serializer().descriptor)
460+
val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors)
461+
println(schemas)
462+
}
463+
```
464+
> You can get the full code [here](../guide/example/example-formats-08.kt).
465+
466+
Which would output as follows.
467+
468+
```text
469+
syntax = "proto2";
470+
471+
472+
// serial name 'example.exampleFormats08.SampleData'
473+
message SampleData {
474+
required int64 amount = 1;
475+
optional string description = 2;
476+
// WARNING: a default value decoded when value is missing
477+
optional string department = 3;
478+
}
479+
480+
```
481+
482+
<!--- TEST -->
483+
484+
Note that since default values are not represented in ".proto" files, a warning is generated when one appears in the schema.
485+
486+
See the documentation for [ProtoBufSchemaGenerator] for more information.
487+
431488
## Properties (experimental)
432489

433490
Kotlin Serialization can serialize a class into a flat map with `String` keys via
@@ -456,7 +513,7 @@ fun main() {
456513
}
457514
```
458515

459-
> You can get the full code [here](../guide/example/example-formats-08.kt).
516+
> You can get the full code [here](../guide/example/example-formats-09.kt).
460517
461518
The resulting map has dot-separated keys representing keys of the nested objects.
462519

@@ -536,7 +593,7 @@ fun main() {
536593
}
537594
```
538595

539-
> You can get the full code [here](../guide/example/example-formats-09.kt).
596+
> You can get the full code [here](../guide/example/example-formats-10.kt).
540597
541598
As a result, we got all the primitive values in our object graph visited and put into a list
542599
in _serial_ order.
@@ -638,7 +695,7 @@ fun main() {
638695
}
639696
```
640697

641-
> You can get the full code [here](../guide/example/example-formats-10.kt).
698+
> You can get the full code [here](../guide/example/example-formats-11.kt).
642699
643700
Now we can convert a list of primitives back to an object tree.
644701

@@ -729,7 +786,7 @@ fun main() {
729786
}
730787
-->
731788

732-
> You can get the full code [here](../guide/example/example-formats-11.kt).
789+
> You can get the full code [here](../guide/example/example-formats-12.kt).
733790
734791
<!--- TEST
735792
[kotlinx.serialization, kotlin, 9000]
@@ -836,7 +893,7 @@ fun main() {
836893
}
837894
```
838895

839-
> You can get the full code [here](../guide/example/example-formats-12.kt).
896+
> You can get the full code [here](../guide/example/example-formats-13.kt).
840897
841898
We see the size of the list added to the result, letting the decoder know where to stop.
842899

@@ -948,7 +1005,7 @@ fun main() {
9481005

9491006
```
9501007

951-
> You can get the full code [here](../guide/example/example-formats-13.kt).
1008+
> You can get the full code [here](../guide/example/example-formats-14.kt).
9521009
9531010
In the output we see how not-null`!!` and `NULL` marks are used.
9541011

@@ -1076,7 +1133,7 @@ fun main() {
10761133
}
10771134
```
10781135
1079-
> You can get the full code [here](../guide/example/example-formats-14.kt).
1136+
> You can get the full code [here](../guide/example/example-formats-15.kt).
10801137
10811138
As we can see, the result is a dense binary format that only contains the data that is being serialized.
10821139
It can be easily tweaked for any kind of domain-specific compact encoding.
@@ -1270,7 +1327,7 @@ fun main() {
12701327
}
12711328
```
12721329
1273-
> You can get the full code [here](../guide/example/example-formats-15.kt).
1330+
> You can get the full code [here](../guide/example/example-formats-16.kt).
12741331
12751332
As we can see, our custom byte array format is being used, with the compact encoding of its size in one byte.
12761333

@@ -1341,6 +1398,10 @@ This chapter concludes [Kotlin Serialization Guide](serialization-guide.md).
13411398
[ProtoIntegerType.SIGNED]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-s-i-g-n-e-d/index.html
13421399
[ProtoIntegerType.FIXED]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/index.html
13431400

1401+
<!--- INDEX kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema -->
1402+
1403+
[ProtoBufSchemaGenerator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema/-proto-buf-schema-generator/index.html
1404+
13441405
<!--- MODULE /kotlinx-serialization-cbor -->
13451406
<!--- INDEX kotlinx-serialization-cbor/kotlinx.serialization.cbor -->
13461407

docs/serialization-guide.md

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ Once the project is set up, we can start serializing some classes.
140140
* <a name='field-numbers'></a>[Field numbers](formats.md#field-numbers)
141141
* <a name='integer-types'></a>[Integer types](formats.md#integer-types)
142142
* <a name='lists-as-repeated-fields'></a>[Lists as repeated fields](formats.md#lists-as-repeated-fields)
143+
* <a name='protobuf-schema-generator-experimental'></a>[ProtoBuf schema generator (experimental)](formats.md#protobuf-schema-generator-experimental)
143144
* <a name='properties-experimental'></a>[Properties (experimental)](formats.md#properties-experimental)
144145
* <a name='custom-formats-experimental'></a>[Custom formats (experimental)](formats.md#custom-formats-experimental)
145146
* <a name='basic-encoder'></a>[Basic encoder](formats.md#basic-encoder)

guide/example/example-formats-08.kt

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
package example.exampleFormats08
33

44
import kotlinx.serialization.*
5-
import kotlinx.serialization.properties.Properties // todo: remove when no longer needed
6-
import kotlinx.serialization.properties.*
5+
import kotlinx.serialization.protobuf.*
6+
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
77

88
@Serializable
9-
class Project(val name: String, val owner: User)
10-
11-
@Serializable
12-
class User(val name: String)
13-
9+
data class SampleData(
10+
val amount: Long,
11+
val description: String?,
12+
val department: String = "QA"
13+
)
1414
fun main() {
15-
val data = Project("kotlinx.serialization", User("kotlin"))
16-
val map = Properties.encodeToMap(data)
17-
map.forEach { (k, v) -> println("$k = $v") }
15+
val descriptors = listOf(SampleData.serializer().descriptor)
16+
val schemas = ProtoBufSchemaGenerator.generateSchemaText(descriptors)
17+
println(schemas)
1818
}

guide/example/example-formats-09.kt

+7-25
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,17 @@
22
package example.exampleFormats09
33

44
import kotlinx.serialization.*
5-
import kotlinx.serialization.descriptors.*
6-
import kotlinx.serialization.encoding.*
7-
import kotlinx.serialization.modules.*
8-
9-
class ListEncoder : AbstractEncoder() {
10-
val list = mutableListOf<Any>()
11-
12-
override val serializersModule: SerializersModule = EmptySerializersModule
13-
14-
override fun encodeValue(value: Any) {
15-
list.add(value)
16-
}
17-
}
18-
19-
fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
20-
val encoder = ListEncoder()
21-
encoder.encodeSerializableValue(serializer, value)
22-
return encoder.list
23-
}
24-
25-
inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
5+
import kotlinx.serialization.properties.Properties // todo: remove when no longer needed
6+
import kotlinx.serialization.properties.*
267

278
@Serializable
28-
data class Project(val name: String, val owner: User, val votes: Int)
9+
class Project(val name: String, val owner: User)
2910

3011
@Serializable
31-
data class User(val name: String)
12+
class User(val name: String)
3213

3314
fun main() {
34-
val data = Project("kotlinx.serialization", User("kotlin"), 9000)
35-
println(encodeToList(data))
15+
val data = Project("kotlinx.serialization", User("kotlin"))
16+
val map = Properties.encodeToMap(data)
17+
map.forEach { (k, v) -> println("$k = $v") }
3618
}

guide/example/example-formats-10.kt

+1-27
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,6 @@ fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any>
2424

2525
inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
2626

27-
class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
28-
private var elementIndex = 0
29-
30-
override val serializersModule: SerializersModule = EmptySerializersModule
31-
32-
override fun decodeValue(): Any = list.removeFirst()
33-
34-
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
35-
if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE
36-
return elementIndex++
37-
}
38-
39-
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
40-
ListDecoder(list)
41-
}
42-
43-
fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
44-
val decoder = ListDecoder(ArrayDeque(list))
45-
return decoder.decodeSerializableValue(deserializer)
46-
}
47-
48-
inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
49-
5027
@Serializable
5128
data class Project(val name: String, val owner: User, val votes: Int)
5229

@@ -55,8 +32,5 @@ data class User(val name: String)
5532

5633
fun main() {
5734
val data = Project("kotlinx.serialization", User("kotlin"), 9000)
58-
val list = encodeToList(data)
59-
println(list)
60-
val obj = decodeFromList<Project>(list)
61-
println(obj)
35+
println(encodeToList(data))
6236
}

guide/example/example-formats-11.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,8 @@ class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
3737
}
3838

3939
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
40-
ListDecoder(list)
41-
42-
override fun decodeSequentially(): Boolean = true
43-
}
40+
ListDecoder(list)
41+
}
4442

4543
fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
4644
val decoder = ListDecoder(ArrayDeque(list))

guide/example/example-formats-12.kt

+8-16
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@ class ListEncoder : AbstractEncoder() {
1313

1414
override fun encodeValue(value: Any) {
1515
list.add(value)
16-
}
17-
18-
override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder {
19-
encodeInt(collectionSize)
20-
return this
21-
}
16+
}
2217
}
2318

2419
fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
@@ -29,26 +24,23 @@ fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any>
2924

3025
inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value)
3126

32-
class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
27+
class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
3328
private var elementIndex = 0
3429

3530
override val serializersModule: SerializersModule = EmptySerializersModule
3631

3732
override fun decodeValue(): Any = list.removeFirst()
38-
33+
3934
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
40-
if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE
35+
if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE
4136
return elementIndex++
4237
}
4338

4439
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
45-
ListDecoder(list, descriptor.elementsCount)
40+
ListDecoder(list)
4641

4742
override fun decodeSequentially(): Boolean = true
48-
49-
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
50-
decodeInt().also { elementsCount = it }
51-
}
43+
}
5244

5345
fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
5446
val decoder = ListDecoder(ArrayDeque(list))
@@ -58,13 +50,13 @@ fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>
5850
inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
5951

6052
@Serializable
61-
data class Project(val name: String, val owners: List<User>, val votes: Int)
53+
data class Project(val name: String, val owner: User, val votes: Int)
6254

6355
@Serializable
6456
data class User(val name: String)
6557

6658
fun main() {
67-
val data = Project("kotlinx.serialization", listOf(User("kotlin"), User("jetbrains")), 9000)
59+
val data = Project("kotlinx.serialization", User("kotlin"), 9000)
6860
val list = encodeToList(data)
6961
println(list)
7062
val obj = decodeFromList<Project>(list)

guide/example/example-formats-13.kt

+3-9
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ class ListEncoder : AbstractEncoder() {
1919
encodeInt(collectionSize)
2020
return this
2121
}
22-
23-
override fun encodeNull() = encodeValue("NULL")
24-
override fun encodeNotNullMark() = encodeValue("!!")
2522
}
2623

2724
fun <T> encodeToList(serializer: SerializationStrategy<T>, value: T): List<Any> {
@@ -34,7 +31,7 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
3431

3532
class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
3633
private var elementIndex = 0
37-
34+
3835
override val serializersModule: SerializersModule = EmptySerializersModule
3936

4037
override fun decodeValue(): Any = list.removeFirst()
@@ -51,8 +48,6 @@ class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : Abstr
5148

5249
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
5350
decodeInt().also { elementsCount = it }
54-
55-
override fun decodeNotNullMark(): Boolean = decodeString() != "NULL"
5651
}
5752

5853
fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
@@ -63,16 +58,15 @@ fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>
6358
inline fun <reified T> decodeFromList(list: List<Any>): T = decodeFromList(list, serializer())
6459

6560
@Serializable
66-
data class Project(val name: String, val owner: User?, val votes: Int?)
61+
data class Project(val name: String, val owners: List<User>, val votes: Int)
6762

6863
@Serializable
6964
data class User(val name: String)
7065

7166
fun main() {
72-
val data = Project("kotlinx.serialization", User("kotlin") , null)
67+
val data = Project("kotlinx.serialization", listOf(User("kotlin"), User("jetbrains")), 9000)
7368
val list = encodeToList(data)
7469
println(list)
7570
val obj = decodeFromList<Project>(list)
7671
println(obj)
7772
}
78-

0 commit comments

Comments
 (0)