Customizations
Getting started with scalapb.proto#
ScalaPB's code generator provides supports many different customizations. To
get access to these customizations, you need to import scalapb/scala.proto
in the proto files you want to customize. You can also have the options apply
to an entire proto3 package by using package-scoped options (see below).
To have scalapb/scalapb.proto available to be imported in your project, add
the following SBT setting in your build.sbt:
If you are invoking protoc manually, you will need to ensure that the files in
protobuf
directory are available to your project.
ScalaPB File-level Options#
ScalaPB file-level options lets you
- specify the name of the Scala package to use (the default is using the java package name).
- request that ScalaPB will not append the protofile name to the package name.
- specify Scala imports so custom base traits and custom types (see below) do not require the full class name.
The file-level options are not required, unless you are interested in those customizations. If you do not want to customize the defaults, you can safely skip this section.
File-level options#
scopecontrols whether the specified options apply only for this proto files or for the entire package. Default isFILE. See package-scoped options for more details.package_namesets the Scala base package name, if this is not defined, then it falls back to thejava_packageoption. If thejava_packageoption is also not the found, then the package name from file'spackagestatement is used.Setting
flat_packageto true (default isfalse) makes ScalaPB not append the protofile base name to the package name. You can also apply this option globally to all files by adding it to your ScalaPB SBT Settings.The
single_fileoption makes the generator output all messages and enums to a single Scala file.The
java_conversionsoptions tells ScalaPB to generate converters to the corresponding Java messages in this file. It does not automatically trigger Java source code generation for the messages. If you need to generate source code in Java, includePB.gens.javain the list of targets in sbt-protoc.The
preambleis a list of strings that is output at the top of the generated Scala file. This option requiressingle_fileto be set. It is commonly used to define sealed traits that are extended using(scalapb.message).extends- see custom base traits below and this example.The
object_nameoption lets you customize the name of the generated class that contains various file-level members such as descriptors and a list of companion objects for the generated messages and enums. This is useful in case you are running into issues where the generated class name conflicts with other things in your project.Setting
lensestofalseinhibits generation of lenses (default istrue).Setting
getterstofalseinhibits generation of getters (default istrue).Setting
retain_source_code_infototrueretains information in the descriptor that can be used to retrieve source code information from the descriptor at runtime (such as comments and source code locations). This option is turned off by default to conserve source size and memory at runtime. When this option is enabled, use thelocationmethod on various descriptors to access source code information.By default, all non-required fields have default values in the constructor of the generated case classes. When setting
no_default_values_in_constructortotrueno default values will be generated for all fields. There is also a message-levelno_default_values_in_constructorand field-levelno_default_value_in_constructor. If the field-level setting is set, it overrides the message-level. If the message-level setting is set, it overrides the file-level setting.Typically, enum values appear in UPPER_CASE in proto files, and ScalaPB generates case objects with exactly the same name in Scala. If you would like ScalaPB to transform the names into CamelCase, set
enum_value_namingtoCAMEL_CASE.It is a common practice in protobufs to prefix each enum value name with the name of the enum. For example, an enum name
Sizemay have values namedSIZE_SMALLandSIZE_LARGE. When you setenum_strip_prefixtotrue, ScalaPB will strip the enum's name from each value name, and they would becomeSMALLandLARGE. Then the name can be transformed to camel-case according toenum_value_naming. Note that the prefix that is removed is the all-caps version of the enum name followed by an underscore.By default, during deserialization only known fields are retained. When setting
preserve_unknown_fieldstotrue, all generated messages in this file will preserve unknown fields. This is default behaviour in java for Proto3 messages since 3.5.0. In ScalaPB 0.10.0: the default of this field becametruefor consistency with Java.Use
bytes_typeto customize the Scala type used for thebytesfield type. You will need to have an implicitTypeMapper[com.google.protobuf.ByteString, YourType]instance so ScalaPb can convert back and forth to the type of your choice. That implicit will be found if it is defined underYourTypecompanion object, or on a package object that matches the generated code (or any of its parent packages).By default, ScalaPB generates Scala sources that are compatible with both Scala 2 and Scala 3. To generate sources that can be compiled error-free with
-source featureon Scala 3 or with-Xsource:3on Scala 2.13, setscala3_sourcestotrueor pass thescala3_sourcesgenerator parameter.Use
public_constructor_parametersto make constructor parameters public, including defaults and TypeMappers. This is helpful for automated schema derivation with e.g.magnoliawhen trying to also derive default fields by using the compiler flag-Yretain-trees. Without this flag, the companion object's_typemapper_*fields are private.
Package-scoped options#
Note: this option is available in ScalaPB 0.8.2 and later.
Sometimes you want to have the same file-level options applied to all
the proto files in your project. To accomplish that, add a package.proto
file (the name does not matter) next to your proto files that looks like this:
All the options in this file will be applied to all proto files in the
package com.mypackage and its sub-packages.
There is no need to explicitly import this file from other protos. If you are
using sbt-protoc and the file is in the proto source directory (default is
src/main/protobuf) then the file will be found and the options applied. If
you are invoking protoc in another way, you need to ensure that this
file is passed to protoc together with the rest of the files.
If you are generating Scala code for proto files that you don't own, you can
use this feature to customize code generation by creating a package.proto
file for that third-party package and include it within your proto source
directory.
The following rules are applied when validating package-scoped options:
- At most one file in each package may provide package-scoped options.
- Sub-packages may override package-scoped options provided by their parent
packages. The options are merged using the Protocol Buffers
mergeFromsemantics. Specifically, this implies that repeated fields such asimportandpreambleare concatenated. - Proto files get the most specific package-scoped options for the package
they are in. File-level options defined in a proto file get merged with the
package-level options using
mergeFrom. - Proto files with package-scoped options must have a
packagestatement. This is to prevent the possibility of options applied globally. Standard classes that are shipped with ScalaPB already assume certain options, so overriding options globally may lead to compilation errors.
Publishing package-scoped options#
If you are publishing a library that includes protos with package-scoped options, you need to make sure your library users source the package-scoped option proto file so the customizations are applied when they generate code.
Your users can simply import your package-scoped options from any proto file in their project to have the settings applied (a single import of the package-scoped options file would apply it globally for the code generator). However, since ScalaPB 0.10.11 and sbt-protoc 1.0.1, sbt-protoc provides a way to automate this with no need to manually import the package-scoped options file. This is accomplished by including a special attribute in the manifest of the library you publish. Add the following to your library's settings:
The path above is relative to the root directory of the published JAR (so src/main/protobuf is not needed). Users add your library to their projects like this:
The first dependency provides the precompiled class files. The second dependency makes it possible
for users to import the protos in the jar file. sbt-protoc will look for the
ScalaPB-Options-Proto attribute in the jar's manifest and automatically add the package scoped options file
to the protoc command line.
note
Since the package-scoped options file is used as a source file in multiple projects, it should not define any types (messages, enums, services). This ensures that the package-scoped proto file does not generate any code on its own so we don't end up with duplicate class files.
Disabling package-scoped options processing#
As a consumer of third-party dependencies that come with options proto, you can disable the behavior of automatically adding the options proto to protoc by setting
in sbt. In that case, it is your responsibility to either manually import the option protos in one
of your own project source files so it gets applied, or ensure that the
generator settigs used in your project are consistent with the ones used to
generate the dependency. Differences in settings can lead to generated code
that does not compile.
Auxiliary options#
In some situations, you may want to set some options in a proto file, but without modifying the original proto file or adding anything ScalaPB-specific to it. To accomplish that, you can define auxiliary options under package-scoped options.
For example, if you are given this proto file:
You can add a file package.proto with the following content:
The list aux_message_options contains options targeted at different messages define under the same proto package of the package-scoped options. The target name needs to be fully-qualified message name in the protobuf namespace. Similar to aux_message_options, we also have aux_enum_options, aux_enum_value_options and aux_field_options. See example usage here. If the target is set * then the options will be
applied to all the entities in the file or package (depending on the scope option).
Primitive wrappers#
In proto 3, unlike proto 2, primitives are not wrapped in an option by default.
The standard technique to obtain an optional primitive is to wrap it inside a
message (since messages are provided inside an Option). Google provides
standard wrappers to the primitive types in
wrappers.proto.
primitive_wrappers is enabled by default for ScalaPB>=0.6.0. Whenever one
of the standard wrappers is used, it will be mapped to Option[X] where X
is a primitive type. For example:
would generate
To disable primitive wrappers in a file:
In versions of ScalaPB prior to 0.6.0, primitive wrappers had to be turned on manually in each file:
Custom base traits for messages#
Note: this option is available in ScalaPB 0.6.1 and later.
ScalaPBs allows you to specify custom base traits to a generated case class. This is useful when you have a few messages that share common fields and you would like to be able to access those fields through a single trait.
Example:
In your code, define the base trait BaseCustomer and include any subset of the fields:
You can specify any number of base traits for a message.
It is also possible to make the generated companion classes extend a class
or trait, by using the companion_extends option. For example:
Will generate a case class that extends MySuperClass, and the companion
object will extend MySuperCompanionClass.
Custom base traits for sealed oneofs#
Since 0.9.0, you can use sealed_one_extends to define one or more base traits for a generated SealedOneof.
Since 0.11.16, you can also add base traits to the empty case object using sealed_oneof_empty_extends.
Use the following options to
As of ScalaPB 0.11.11 you may also use following option to make the generated sealed oneof
trait universal.
It may be useful when your sealed oneof variants are value-classes (e.g. extends AnyVal)
Custom base traits for sealed oneofs companion objects#
Note: this option is available in ScalaPB 0.11.11 and later.
Use the following option to define one or more base traits for a generated SealedOneof companion object:
Custom base traits for enums#
In a similar fashion to custom base traits for messages, it is possible to define custom base traits for enum types, for the companion objects of enum types and even for specific values.
For example:
The generated code will look something like this:
Custom types#
You can customize the Scala type of any field. One use-case for this is when
you would like to use type-safe wrappers around primitive values to enforce unit
correctness. For example, instead of using a raw integer for time fields, you can
wrap them in a Seconds class.
We would like to write code like this:
How will ScalaPB know how to convert from the original type (Integer) to the
custom type Seconds? For each custom type you need to define an implicit
TypeMapper that will tell ScalaPB how to convert between the custom type and
the base Scala type. A good place to define this implicit is in the companion
class for your custom type, since the Scala compiler will look for a
typemapper there by default. If your typemapper is defined elsewhere, you
will need to import it manually by using the import file-level option.
TypeMapper takes two function parameters. The first converts from the original type to
the custom type. The second function converts from the custom type to the
original type.
In addition to primitive values, you can customize enums and messages as well.
For more examples, see:
If you have a TypeMapper that maps a generated type into a type you don't own
(such as String, or a third-party class) then you don't have access to the
companion object to define the typemapper in. Instead, you can place the
typemapper in one of the parent package objects of the generated code. For
example, if you want to map an enum to a string, and the message containing it
goes into the a.b.c package, you can define the type mapper like this:
Message-level custom type and boxing#
In the previous section you saw how to customize the type generated for a
specific field. ScalaPB also lets you specify a custom type at the message
level. When type is set at the message level, that type is used for all the
fields that use that message. This eliminates the need to specify type on each field
of this type.
In a Scala file define an implicit mapper:
Now, each time you reference Duration in a proto file, the generated field in Scala code
will be of type MyDuration:
If you do not want any instance of your message to be boxed (regardless if it
has a custom type), you can set no_box at the message-level:
Then when this message is used, it will not be wrapped in an Option. If
no_box is specified at the field level, it overrides the value specified at
the message level.
Custom types on maps#
Since version 0.6.0 it is possible to customize the key and value types of
maps. Like the custom types described above you will need to have a TypeMapper
for the custom type.
Example:
Example: see CustomMaps in maps.proto
You can also customize the collection type used for a map. See the next section for details.
Custom collection types#
By default, ScalaPB compiles repeated fields into a Seq[T]. When a message
is parsed from bytes, the default implementation instantiates a Vector[T],
which is a subtype of Seq[T]. You can instruct ScalaPB to use a different
collection type for one field by specifying the collection_type option. You
can also specify a collection_type for the entire proto file by specifying a
collection_type at the file-level.
If both are defined then the field-level setting wins.
Similar to collection_type, we have map_type for map types. By default,
ScalaPB generates scala.collection.immutable.Map for maps, and you can
customize it at the field level, or file-level by specifying a map_type
option.
map_type was introduced in ScalaPB 0.8.5.
Note on mutable collection: ScalaPB assumes that all data is immutable. For example, the result
of serializedSize is cached in a private field. When choosing mutable collections, you must be
careful not to mutate any collection after it has been passed to any message, or you might get some
surprising results!
Note: using Array is not supported along with Java conversions.
Note: Most Scala collections can be used with this feature. If you are trying
to implement your own collection type, it may be useful to check MyVector
and MyMap, the simplest custom collection that is compatible with ScalaPB:
Custom names#
Sometimes it may be useful to manually specify the name of a field in the
generated code. For example, if you have a field named hash_code, then the
camel-case version of it would be hashCode. Since that name would conflict with
the hashCode() method
we inherit from Java, ScalaPB issues an error. You can tell ScalaPB to use an
alternative name by using the scala_name option:
It is also possible to customize the Scala name of an enum value:
The same customization can be applied to oneof fields:
Adding annotations#
Since ScalaPB 0.6.3, you can add annotations to the generated case classes like this:
In ScalaPB 0.7.0, you can add annotations to the companion object of a message and to individual fields:
In ScalaPB 0.10.9, you can also add annotations to the auto generated unknownFields field:
In ScalaPB 0.11.4, you can also add annotations to the enum values and the Unrecognized case class:
Adding derives clause#
In ScalaPB 0.11.14, it is possible to add a derives clause to generated messages and
sealed oneofs: