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).