Writing generic code
In some cases you will want to write generic code that can handle arbitrary message types. This guide will show you various techniques that you can use to accomplish that.
Generated types
For each message in your proto files, ScalaPB will generate a case class which
extends the GeneratedMessage[MessageType] and a companion object:
case class MyMessage(...) extends scalapb.GeneratedMessage
object MyMessage extends scalapb.generatedMessageCompanion[MyMessage]
Generally speaking, the case class would have methods that act on a given instance. Here are the important ones:
trait GeneratedMessage {
// Serializes this message to the given output stream.
def writeTo(output: OutputStream): Unit
// Serializes this message into an array of bytes
def toByteArray: Array[Byte] = {
// Returns the companion object of this message.
def companion: GeneratedMessageCompanion[_]
// Returns a proto string representation of this message.
def toProtoString: String
}
The companion, in turn, provides methods to create new instances of the type,
as well as retrieving the descriptors of the messages. The descriptors can be
used to query structural information about the proto files at runtime. Note
that the GeneratedMessageCompanion takes a type parameter A which
represents the message case class (must be a subtype of GeneratedMessage):
trait GeneratedMessageCompanion[A <: GeneratedMessage] {
// Parses a message of type A from a byte array.
def parseFrom(s: Array[Byte]): A
// Returns the Java descriptors
def javaDescriptor: com.google.protobuf.Descriptors.Descriptor
// Returns the Scala descriptors
def scalaDescriptor: scalapb.descriptors.Descriptor
// Returns the companion for a field. The field must be a message field.
def messageCompanionForFieldNumber(field: Int): GeneratedMessageCompanion[_]
}
Writing a function that accepts any message type
The following is an example of a function that takes an instance of a message and writes it to a file.
import java.nio.file.{Files, Paths}
import scalapb.GeneratedMessage
def writeToFile[A <: GeneratedMessage](msg: A, fileName: String): Unit = {
Files.write(Paths.get(fileName), msg.toByteArray)
()
}
Summoning the companion
In our next example, we will write a function that reads a file and parse it
into a message of type A. To build a new instance of a message, we need to
call parseFrom on the companion. Using Scala's implicit search, it is easy
to obtain the companion of any message type:
import java.nio.file.{Files, Paths}
import scalapb.{GeneratedMessage, GeneratedMessageCompanion}
def readFromFile[A <: GeneratedMessage](fileName: String)(
implicit cmp: GeneratedMessageCompanion[A]): A = {
val byteArray = Files.readAllBytes(Paths.get(fileName))
cmp.parseFrom(byteArray)
}
When calling this function, you need to provide the specific type you want it
to return, and the filename. The Scala compiler will automatically find the
appropriate message companion to pass as cmp via implicit search:
readFromFile[Person]("/tmp/person.pb")