
Supports Protocol Buffers 3 with features like serialization, .proto document handling, and Protoscope language support for testing, offering extensive document comparison and validation functionalities.
A comprehensive Protobuf 3 library for Kotlin Multiplatform, offering features ranging from
serialization/deserialization with kotlinx.serialization to .proto document handling capabilities
and Protoscope language support.
Currently, the library supports JVM and JS platforms. Support for Native and WASM platforms will be looked into in future versions.
Note that this library is still in its early stages, and the API is subject to change in any 0.x version.
You can obtain the required libraries from Maven Central. In Gradle projects, add this to you build.gradle.kts file:
repositories {
mavenCentral()
}See the examples below for which artifacts you need to include for which use case. For non-multiplatform projects,
you have to suffix the artifact name with the compilation target, i.e. -jvm or -js.
Include the following dependency in your build.gradle.kts file:
dependencies {
implementation("pro.felixo:protobuf-kotlin-serialization:<version>")
}To serialize and deserialize Protobuf, create an EncodingSchema with the SerialDescriptors of the messages you want
it to support, and use it to encode/decode messages:
import pro.felixo.protobuf.serialization.generation.encodingSchema
@Serializable
data class Person(val name: String, val age: Int)
val schema = encodingSchema(listOf(Person.serializer().descriptor))
val bytes = schema.encodeToByteArray(Person("Catalina", 2))
val person = schema.decodeFromByteArray<Person>(bytes)In encodingSchema, specify a SerializersModule to support open polymorphic serialization or contextual
serialization. In this case, you also need to specify which types from the SerializersModule to include in the schema.
For compatibility especially with Protobuf 2, you can pass encodeZeroValues = true to encodingSchema. This controls
whether fields that have their Protobuf default values (zero/empty) are encoded or omitted.
This library is completely agnostic of the optionality of fields in terms of kotlinx.serialization:
fields that are nullable in Kotlin will be made optional in Protobuf, and the default values to use when decoding
a message with absent fields are dictated by the Protobuf spec (zero for numbers, empty for length-delimited fields).
This means that in order to avoid unexpected behaviour, in your serializable classes, only nullable fields should have
default values, and that default value should be null.
The schema that is generated from Kotlin types can be customized using annotations:
@Serializable
data class Person(
@ProtoNumber(3) // field number: 3
@SerialName("fullName") // field name: "fullName"
val name: String,
@ProtoNumber(1) // field number: 1
@ProtoIntegerType(IntegerType.Fixed) // use fixed32 rather than the default, int32
val age: Int,
@ProtoListItem(
integerType = IntegerType.Signed, // use sint64 rather than the default, int64
// Since this is a list of a nullable type, a synthetic message is generated to represent the list elements.
// The following parameters control the schema of that synthetic message.
messageName = "FavoriteNumber",
fieldName = "number",
fieldNumber = 5
)
val favoriteNumbers: List<Long?>, // will be auto-assigned the field number: 2
// For maps, the schema of the synthetic message that represents the map entries can be customized as follows.
@ProtoMapEntry(
messageName = "PetEntry",
keyName = "name",
keyNumber = 3,
keyIntegerType = IntegerType.Fixed, // map key is not an integer, so this is ignored
valueName = "pet",
valueNumber = 4,
valueIntegerType = IntegerType.Signed // map value is not an integer, so this is ignored
)
val pets: Map<String, Pet>, // will be auto-assigned the field number: 4
val species: Species // will be auto-assigned the field number: 5
)
@Serializable
enum class Species {
@ProtoDefaultEnumValue
Unknown,
@ProtoNumber(1)
Human,
@ProtoNumber(2)
Gorn
}For more usage examples, see the serialization integration tests.
The module protobuf-kotlin-schemadocument provides support for .proto schema documents. It can be used to read
and write .proto documents, and to compare them for equality or equivalence. It also provides some support for
schema validation.
To parse a schema document, use the SchemaDocumentReader class:
import pro.felixo.protobuf.schemadocument.SchemaDocumentReader
val schemaDocument = SchemaDocumentReader().readSchema("...schema text...")To generate a document from an encoding schema, use the toSchemaDocument extension function:
import pro.felixo.protobuf.serialization.generation.encodingSchema
import pro.felixo.protobuf.serialization.toSchemaDocument
@Serializable
data class Person(val name: String, val age: Int)
val schema = encodingSchema(listOf(Person.serializer().descriptor))
val schemaDocumentForEncodingSchema = encodingSchema.toSchemaDocument()To compare two schema documents for equality, use the == operator. Note that
SchemaDocument does have a notion of the order of declarations, so two equivalent documents may be considered unequal
because the orders of their declarations differ:
val equal = schemaDocument == schemaDocumentForEncodingSchemaIn order to compare two schema documents for equivalence, which is useful for testing purposes, use the areEquivalent
function:
import pro.felixo.protobuf.schemadocument.areEquivalent
val equivalent = areEquivalent(schemaDocument, schemaDocumentForEncodingSchema)
To validate a document against the Protobuf schema validation rules, use the validate function:
import pro.felixo.protobuf.schemadocument.validatation.validate
val validationResult: ValidationResult = validate(schemaDocument)The protobuf-kotlin-protoscope module implements
the Protoscope language, which allows for the
textual representation of the protobuf wire format and is useful for testing purposes.
Protoscope code can be tokenized and converted to its binary representation. Note that the reverse is not currently supported.
import pro.felixo.protobuf.protoscope.ProtoscopeConverter
val bytes = ProtoscopeConverter().convert("""1: {"Catalina"} 2: 2 """)
// bytes now can be compared to the serialized form of a Person message, to assert its correctnessMajor future roadmap items are:
Major features that are not included and may or may not be implemented in the future are:
kotlinx.serialization. In order to support other frameworks, this could be separated.kotlinx.serialization, a protoc compiler
plugin could be written that generates Kotlin multiplatform code.All contributions, whether issues raised or pull requests submitted, are appreciated. If you report a bug, please consider including a unit test that demonstrates it, even if you aren't providing a fix.
This project is licensed under the MIT License.
For any questions, issues, or discussions, feel free to:
A comprehensive Protobuf 3 library for Kotlin Multiplatform, offering features ranging from
serialization/deserialization with kotlinx.serialization to .proto document handling capabilities
and Protoscope language support.
Currently, the library supports JVM and JS platforms. Support for Native and WASM platforms will be looked into in future versions.
Note that this library is still in its early stages, and the API is subject to change in any 0.x version.
You can obtain the required libraries from Maven Central. In Gradle projects, add this to you build.gradle.kts file:
repositories {
mavenCentral()
}See the examples below for which artifacts you need to include for which use case. For non-multiplatform projects,
you have to suffix the artifact name with the compilation target, i.e. -jvm or -js.
Include the following dependency in your build.gradle.kts file:
dependencies {
implementation("pro.felixo:protobuf-kotlin-serialization:<version>")
}To serialize and deserialize Protobuf, create an EncodingSchema with the SerialDescriptors of the messages you want
it to support, and use it to encode/decode messages:
import pro.felixo.protobuf.serialization.generation.encodingSchema
@Serializable
data class Person(val name: String, val age: Int)
val schema = encodingSchema(listOf(Person.serializer().descriptor))
val bytes = schema.encodeToByteArray(Person("Catalina", 2))
val person = schema.decodeFromByteArray<Person>(bytes)In encodingSchema, specify a SerializersModule to support open polymorphic serialization or contextual
serialization. In this case, you also need to specify which types from the SerializersModule to include in the schema.
For compatibility especially with Protobuf 2, you can pass encodeZeroValues = true to encodingSchema. This controls
whether fields that have their Protobuf default values (zero/empty) are encoded or omitted.
This library is completely agnostic of the optionality of fields in terms of kotlinx.serialization:
fields that are nullable in Kotlin will be made optional in Protobuf, and the default values to use when decoding
a message with absent fields are dictated by the Protobuf spec (zero for numbers, empty for length-delimited fields).
This means that in order to avoid unexpected behaviour, in your serializable classes, only nullable fields should have
default values, and that default value should be null.
The schema that is generated from Kotlin types can be customized using annotations:
@Serializable
data class Person(
@ProtoNumber(3) // field number: 3
@SerialName("fullName") // field name: "fullName"
val name: String,
@ProtoNumber(1) // field number: 1
@ProtoIntegerType(IntegerType.Fixed) // use fixed32 rather than the default, int32
val age: Int,
@ProtoListItem(
integerType = IntegerType.Signed, // use sint64 rather than the default, int64
// Since this is a list of a nullable type, a synthetic message is generated to represent the list elements.
// The following parameters control the schema of that synthetic message.
messageName = "FavoriteNumber",
fieldName = "number",
fieldNumber = 5
)
val favoriteNumbers: List<Long?>, // will be auto-assigned the field number: 2
// For maps, the schema of the synthetic message that represents the map entries can be customized as follows.
@ProtoMapEntry(
messageName = "PetEntry",
keyName = "name",
keyNumber = 3,
keyIntegerType = IntegerType.Fixed, // map key is not an integer, so this is ignored
valueName = "pet",
valueNumber = 4,
valueIntegerType = IntegerType.Signed // map value is not an integer, so this is ignored
)
val pets: Map<String, Pet>, // will be auto-assigned the field number: 4
val species: Species // will be auto-assigned the field number: 5
)
@Serializable
enum class Species {
@ProtoDefaultEnumValue
Unknown,
@ProtoNumber(1)
Human,
@ProtoNumber(2)
Gorn
}For more usage examples, see the serialization integration tests.
The module protobuf-kotlin-schemadocument provides support for .proto schema documents. It can be used to read
and write .proto documents, and to compare them for equality or equivalence. It also provides some support for
schema validation.
To parse a schema document, use the SchemaDocumentReader class:
import pro.felixo.protobuf.schemadocument.SchemaDocumentReader
val schemaDocument = SchemaDocumentReader().readSchema("...schema text...")To generate a document from an encoding schema, use the toSchemaDocument extension function:
import pro.felixo.protobuf.serialization.generation.encodingSchema
import pro.felixo.protobuf.serialization.toSchemaDocument
@Serializable
data class Person(val name: String, val age: Int)
val schema = encodingSchema(listOf(Person.serializer().descriptor))
val schemaDocumentForEncodingSchema = encodingSchema.toSchemaDocument()To compare two schema documents for equality, use the == operator. Note that
SchemaDocument does have a notion of the order of declarations, so two equivalent documents may be considered unequal
because the orders of their declarations differ:
val equal = schemaDocument == schemaDocumentForEncodingSchemaIn order to compare two schema documents for equivalence, which is useful for testing purposes, use the areEquivalent
function:
import pro.felixo.protobuf.schemadocument.areEquivalent
val equivalent = areEquivalent(schemaDocument, schemaDocumentForEncodingSchema)
To validate a document against the Protobuf schema validation rules, use the validate function:
import pro.felixo.protobuf.schemadocument.validatation.validate
val validationResult: ValidationResult = validate(schemaDocument)The protobuf-kotlin-protoscope module implements
the Protoscope language, which allows for the
textual representation of the protobuf wire format and is useful for testing purposes.
Protoscope code can be tokenized and converted to its binary representation. Note that the reverse is not currently supported.
import pro.felixo.protobuf.protoscope.ProtoscopeConverter
val bytes = ProtoscopeConverter().convert("""1: {"Catalina"} 2: 2 """)
// bytes now can be compared to the serialized form of a Person message, to assert its correctnessMajor future roadmap items are:
Major features that are not included and may or may not be implemented in the future are:
kotlinx.serialization. In order to support other frameworks, this could be separated.kotlinx.serialization, a protoc compiler
plugin could be written that generates Kotlin multiplatform code.All contributions, whether issues raised or pull requests submitted, are appreciated. If you report a bug, please consider including a unit test that demonstrates it, even if you aren't providing a fix.
This project is licensed under the MIT License.
For any questions, issues, or discussions, feel free to: