A Kotlin Multiplatform, code-first, reflection-free Protobuf DSL. Define messages by declaring PbSchema singletons with PbType fields, then serialize/deserialize without reflection or codegen.
The framework is previously used in Acidify.
The goal is a compact, readable DSL that favors expressiveness over maximal performance.
Define a message model:
object TestMessage : PbSchema() {
val int32Field = PbInt32[1]
val boolField = PbBoolean[2]
val optionalField = PbOptional[PbInt32[3]]
}Construct, serialize, and deserialize:
val message = TestMessage {
it[int32Field] = 1
it[boolField] = true
it[optionalField] = null
}
val encoded = message.toByteArray()
val decoded = TestMessage(encoded)
val decodedInt32Field = decoded.get { int32Field }
// Equivalent to: decoded[TestMessage.int32Field]- Use
object : PbSchemato define models and avoid reflection limits on non-JVM targets. - Call
TestMessage { ... }(viaoperator fun invoke) to create aPbObject<TestMessage>. - Use
it[field] = valueto set fields; useget { field }to read fields.
PbType handles conversion between values and low-level DataTokens. Common types:
- Scalars:
PbInt32,PbInt64,PbBoolean,PbFixed32,PbFixed64 - String/bytes:
PbString,PbBytes - Optional:
PbOptional[PbType] - Repeated:
PbRepeatedInt32,PbRepeatedInt64,PbRepeatedString,PbRepeatedBytes - Nested messages:
PbMessage(fieldNumber, schema),PbRepeatedMessage(fieldNumber, schema)
Example:
object Nested : PbSchema() {
val id = PbInt32[1]
}
object Parent : PbSchema() {
val nested = PbMessage(1, Nested)
val repeatedNested = PbRepeatedMessage(2, Nested)
}This project does not aim for extreme performance. The focus is on DSL ergonomics and cross-platform usability; the overhead is acceptable for typical use cases.
- Related article (Chinese) that explains the design and implementation of this project