Skip to content

Improve Protobuf OneOf type declarations to avoid an intermediate OneOf Type class #2918

Open
@rocketraman

Description

@rocketraman

What is your use-case and why do you need this feature?

I am investigating using Protobuf to serialize polymorphic types. Using an example similar to the one in the guide, I have to declare my an example hierarchy that itself contains nested classes like this:

@Serializable
data class Data(
  @ProtoNumber(1) val name: String,
  @ProtoOneOf val phone: IPhoneType?,
)

@Serializable
sealed interface IPhoneType

@Serializable
class HomePhoneType(
  @ProtoNumber(2) val homePhone: HomePhone,
): IPhoneType

@Serializable
class WorkPhoneType(
  @ProtoNumber(3) val workPhone: WorkPhone,
): IPhoneType

@Serializable
data class HomePhone(val number: String)

@Serializable
data class WorkPhone(val number: String, val extension: String)

This results in inconsistent serialization between JSON and Protobuf, the former of which serializes like this:

{
    "name": "Something",
    "phone": {
        "type": "HomePhoneType",
        "homePhone": {
            "foo": "abc"
        }
    }
}

and the latter of which uses a cleaner schema with no trace of the intermediate type:

syntax = "proto2";


message Data {
  required string name = 1;
  oneof phone {
    HomePhone homePhone = 2;
    WorkPhone workPhone = 3;
  }
}

message HomePhone {
  required string foo = 1;
}

message WorkPhone {
  required string bar = 1;
}

Furthermore, the natural way of describing such a data structure in Kotlin would be like this:

@Serializable
data class Data(
  val name: String,
  val phone: IPhone,
)

@Serializable
sealed class IPhone

@Serializable
data class HomePhone(val number: String): IPhone()

@Serializable
data class WorkPhone(val number: String, val extension: String): IPhone()

which would result in the expected JSON, but of course does not work with Protobuf OneOf.

Describe the solution you'd like

I'd like to define the data structure in an idiomatic way, and have the ProtoBuf OneOf work as expected.

It seems like the only value the intermediate OneOf type adds is the definition of the @ProtoNumber for the OneOf.

Therefore, maybe we could define the data structure in Kotlin using annotations like the following:

@Serializable
data class Data(
  @ProtoNumber(1) val name: String,
  @ProtoOneOf val phone: IPhone,
)

@Serializable
sealed class IPhone

@Serializable
@ProtoOneOfNumber(2)
data class HomePhone(val number: String): IPhone()

@Serializable
@ProtoOneOfNumber(3)
data class WorkPhone(val number: String, val extension: String): IPhone()

The result of this would be an idiomatic simpler Kotlin data structure, and consistent form of serialized output for JSON and Protobuf.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions