Generated Code
This page describes the code generated by ScalaPB and how to use it.
#
Default Package StructureThe generator will create a Scala file for each top-level message and enum in your proto file. Using multiple files results in a better incremental compilation performance.
The Scala package of the generated files will be determined as follows:
- If the
java_package
option is defined, then the Scala package will bejava_package.base_name
wherebase_name
is the name of the proto file without the.proto
extension. - If the
java_package
is undefined, but the file specifies a package name via thepackage
keyword then the Scala package will bepackage.base_name
. - If neither
java_package
norpackage
are specified, the Scala package will be justbase_name
.
From version 0.4.2, there is a way to customize the package name by using file-level options.
#
MessagesEach message corresponds to a final case class and a companion object.
Optional fields are wrapped in an Option[]
, repeated fields are given as
Seq[]
, and required fields (which you should not use, see "Required is
forever" in the Language
Guide are
normal members.
Note that in proto3, scalar (non-message) fields are not wrapped in Option
,
For example, if your protocol buffer looks like this:
...then the compiler will generate code that looks like this:
The case class contains various methods for serialization, and the companion
object contains method for parsing. See the source code for
GeneratedMessage
and
GeneratedMessageCompanion
to see what methods are available
#
Building New MessagesCreate a new instance of a message by calling the constructor (as you normally would for a case class):
When constructing messages, it is advised to use named arguments Person(name = x, age = y) to ensure your code does not rely on the order of the fields in the protocol buffer definition.
#
Updating MessagesMessages are immutables: once you created a message instance it can not be changed. Messages are thread-safe: you can access the same message instance from multiple threads.
When you want to modify a message, simply create a new one based on the
original. You can use the copy()
method Scala provides for all case classes,
however ScalaPB provides additional methods to make it even easier.
The first method is using a withX()
method, where X
is a name of a field.
For example
Note that when using the withX()
method on an optional field, you do not
need to provide the Some()
.
Another way to update a message is using the update()
method:
The update()
method takes "mutations" (like the assignments above), and
applies them on the object. Using the update()
method, as we will see
below, usually results in a more concise code, especially when your fields are
message types.
#
Optional FieldsOptional fields are wrapped inside an Option
. The compiler will generate a
getX()
method that will return the option's value if it is set, or a
default value for the field if it is unset (that is, if it is None
)
There are two ways to update an optional field using the update()
method:
The first way sets the field with a value, the second way lets you pass an
Option[]
so it is possible to set the field to None
.
For each optional field X
, the compiler also generates a clearX()
method
that returns a new instance of the message which is identical to the original
one except that the field is assigned the value None
.
#
Required FieldsRequired fields have no default value in the generated constructor, so you must specify them when you instantiate a new message. Differences from optional fields:
- The value of the field is not wrapped inside an
Option
. - There is no
getX
method (you can always use.x
) - There is no
clearX
method (since that will result in an invalid message) - There is no
_.optionalX
lens for theupdate()
method.
#
Repeated FieldsRepeated fields are provided as a Seq[T]
. The compiler will generate the
following methods:
addFoo(f1, [f2, f3, ...]: Foo)
: returns a copy with the given elements added to the original list.addAllFoo(fs: Seq[Foo])
: returns a copy with the given sequence of elements added to the original list.withX(otherList)
: replace the sequence with another.clearX
: replace with the sequence with an empty one.
Using update()
is especially fun with repeated fields:
#
Oneof FieldsOneofs are great when you model a message that has multiple disjoint cases. An example use case would be:
The compiler will generate code that looks like this:
This enables writing coding like this:
#
EnumerationsEnumerations are implemented using sealed traits that extend GeneratedEnum
.
This approach, rather than using Scala's standard Enumeration type, allows
getting a warning from the Scala compiler when a pattern match is incomplete.
For a definition like:
The compiler will generate:
And we can write:
#
ASCII RepresentationEach message case-class has toProtoString
method that returns a string
representation of the message in an ASCII format. The ASCII format can be
parsed back by the fromAscii()
method available on the companion object.
That format is not officially documented, but at least the standard Python,
Java and C++ implementations of protobuf attempt to generate (and be able to parse)
compatible ASCII representations. ScalaPB's toString()
and fromAscii
follow the Java implementation.
The format looks like this:
This format can be useful for debugging or for transient data processing, but beware of persisting these ASCII representations: unknown fields throw an exception, and unlike the binary format, the ASCII format is senstitive to renames.
#
Java ConversionsIf you are dealing with legacy Java protocol buffer code, while still wanting
to write new code using ScalaPB, it can be useful to generate converters
to/from the Java protocol buffers. To do this, set Compile / PB.targets
like this in your build.sbt
:
This will result in the following changes:
- The companion object for each message will have
fromJavaProto
andtoJavaProto
methods. - The companion object for enums will have
fromJavaValue
andtoJavaValue
methods.