From e5e703ea0b36015727551acc6dc63f94269c112c Mon Sep 17 00:00:00 2001 From: xiaozhikang Date: Sat, 20 Jan 2024 16:14:10 +0800 Subject: [PATCH] docs for protobuf oneof --- docs/formats.md | 80 ++++++++++++++--- docs/serialization-guide.md | 1 + guide/example/example-formats-08.kt | 23 +++-- guide/example/example-formats-09.kt | 20 ++--- guide/example/example-formats-10.kt | 32 ++----- guide/example/example-formats-11.kt | 28 +----- guide/example/example-formats-12.kt | 6 +- guide/example/example-formats-13.kt | 24 ++--- guide/example/example-formats-14.kt | 12 +-- guide/example/example-formats-15.kt | 78 +++++++--------- guide/example/example-formats-16.kt | 47 +--------- guide/example/example-formats-17.kt | 135 ++++++++++++++++++++++++++++ guide/test/FormatsTest.kt | 44 +++++---- 13 files changed, 313 insertions(+), 217 deletions(-) create mode 100644 guide/example/example-formats-17.kt diff --git a/docs/formats.md b/docs/formats.md index 3fcbf9c82d..6f49510c57 100644 --- a/docs/formats.md +++ b/docs/formats.md @@ -18,6 +18,7 @@ stable, these are currently experimental features of Kotlin Serialization. * [Integer types](#integer-types) * [Lists as repeated fields](#lists-as-repeated-fields) * [Packed fields](#packed-fields) + * [Oneof field (experimental)](#oneof-field-experimental) * [ProtoBuf schema generator (experimental)](#protobuf-schema-generator-experimental) * [Properties (experimental)](#properties-experimental) * [Custom formats (experimental)](#custom-formats-experimental) @@ -435,6 +436,65 @@ Per the standard packed fields can only be used on primitive numeric types. The Per the [format description](https://developers.google.com/protocol-buffers/docs/encoding#packed) the parser ignores the annotation, but rather reads list in either packed or repeated format. +### Oneof field (experimental) + +Kotlin Serialization `ProtoBuf` format supports [oneof](https://protobuf.dev/programming-guides/proto2/#oneof) fields +base on the [Polymorphism](polymorphism.md). + +You can declare a property of your class to be `oneof` by following the contracts: + +* Declare an interface, or abstract class, in represent of the `oneof` group. +* Declare the property with the type added above, annotated with `@ProtoOneOf(ids)` + with all possible proto numbers, not `@ProtoNumber`. +* Declare subclasses from the type with **only one property** each per the oneof group elements. +* Annotated the subclasses with `@ProtoNumber` on the class declaration, not the property, + per the oneof group elements and `@ProtoOneOf(ids)` above. + + + +```kotlin +@Serializable +data class Data( + @ProtoNumber(1) val name: String, + @ProtoOneOf(2, 3) val phone: IPhoneType, +) +@Serializable sealed interface IPhoneType +@Serializable @ProtoNumber(2) @JvmInline value class HomePhone(val number: String): IPhoneType +@Serializable @ProtoNumber(3) data class WorkPhone(val number: String): IPhoneType + +fun main() { + val dataTom = Data("Tom", HomePhone("123")) + val stringTom = ProtoBuf.encodeToHexString(dataTom) + val dataJerry = Data("Jerry", WorkPhone("789")) + val stringJerry = ProtoBuf.encodeToHexString(dataJerry) + println(stringTom) + println(stringJerry) + println(ProtoBuf.decodeFromHexString(stringTom)) + println(ProtoBuf.decodeFromHexString(stringJerry)) +} +``` + +> You can get the full code [here](../guide/example/example-formats-08.kt). + +```text +0a03546f6d1203313233 +0a054a657272791a03373839 +Data(name=Tom, phone=HomePhone(number=123)) +Data(name=Jerry, phone=WorkPhone(number=789)) +``` + + + +In [ProtoBuf diagnostic mode](https://protogen.marcgravell.com/decode) the first 2 lines on output is equivalent to + +``` +Field #1: 0A String Length = 3, Hex = 03, UTF8 = "Tom" Field #2: 12 String Length = 3, Hex = 03, UTF8 = "123" +Field #1: 0A String Length = 5, Hex = 05, UTF8 = "Jerry" Field #3: 1A String Length = 3, Hex = 03, UTF8 = "789" +``` + ### ProtoBuf schema generator (experimental) As mentioned above, when working with protocol buffers you usually use a ".proto" file and a code generator for your @@ -467,7 +527,7 @@ fun main() { println(schemas) } ``` -> You can get the full code [here](../guide/example/example-formats-08.kt). +> You can get the full code [here](../guide/example/example-formats-09.kt). Which would output as follows. @@ -475,7 +535,7 @@ Which would output as follows. syntax = "proto2"; -// serial name 'example.exampleFormats08.SampleData' +// serial name 'example.exampleFormats09.SampleData' message SampleData { required int64 amount = 1; optional string description = 2; @@ -519,7 +579,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-09.kt). +> You can get the full code [here](../guide/example/example-formats-10.kt). The resulting map has dot-separated keys representing keys of the nested objects. @@ -599,7 +659,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-10.kt). +> You can get the full code [here](../guide/example/example-formats-11.kt). As a result, we got all the primitive values in our object graph visited and put into a list in _serial_ order. @@ -701,7 +761,7 @@ fun main() { } ``` -> You can get the full code [here](../guide/example/example-formats-11.kt). +> You can get the full code [here](../guide/example/example-formats-12.kt). Now we can convert a list of primitives back to an object tree. @@ -792,7 +852,7 @@ fun main() { } --> -> You can get the full code [here](../guide/example/example-formats-12.kt). +> You can get the full code [here](../guide/example/example-formats-13.kt).