Field Encoder & Decoder
Field encoders and decoders are automatically provided by protoless for the following types:
- Protobuf scalar types
- Optional fields
- Repeated fields
- Nested fields
- Enumerations
- Value classes
- Other scala types
- Custom field encoder & decoder
Protobuf scalar types
.proto type | Scala type |
---|---|
double | Double |
float | Float |
int32 | Int |
int64 | Long |
uint32 | Int @@ Unsigned |
uint64 | Long @@ Unsigned |
sint32 | Int @@ Signed |
sint64 | Long @@ Signed |
fixed32 | Int @@ Fixed |
fixed64 | Long @@ Fixed |
sfixed32 | Int @@ Signed with Fixed |
sfixed64 | Long @@ Signed with Fixed |
bool | Boolean |
string | String |
bytes | ByteString |
Numeric fields
Even if int32
, uint32
, sint32
, fixed32
and sfixed32
are all represented as an Int
, it’s required to know
how the numeric field has been encoded in order to decode it with the right algorithm.
protoless uses tagged type to carry this information by your model, and provides helpers to tag your data.
import io.protoless._, io.protoless.generic.auto._, io.protoless.tag._
case class NumericSeries(uint: Int @@ Unsigned, sint: Long @@ Signed, fixed: Int @@ Fixed, sfixed: Int @@ Signed with Fixed)
val serie = NumericSeries(
uint = unsigned(1),
sint = signed(2),
fixed = fixed(3),
sfixed = signedFixed(4)
)
// serie: NumericSeries = NumericSeries(1,2,3,4)
messages.Encoder[NumericSeries].encodeAsBytes(serie)
// res1: Array[Byte] = Array(8, 1, 16, 4, 29, 3, 0, 0, 0, 37, 4, 0, 0, 0)
Optional fields
Unlike protocol buffers version 3 which force optional type by default, your model will lay down the law.
You just have to use an Option
if you want to encode/decode an optional field.
Implicit instances of Option[A]
are provided if the type A
has a decoder/encoder instance.
Repeated fields
protoless can decode a repeated
field with any uniary subclass of Traversable:
Seq, List, Set, HashTrieSet, ArrayBuffer… It can also decode cats NonEmptyList
and Java Array
.
Encoders, which are not variant, are a bit more restrictive. protoless provides encoders for Seq
, immutable.Seq
, List
,
Vector
, Stream
, Array
and NonEmptyList
.
You can easily derive new collection encoders:
import io.protoless.fields._
import scala.collection.mutable.ArrayBuffer
implicit def encodeArrayBuffer[A](implicit enc: RepeatableFieldEncoder[A]): FieldEncoder[ArrayBuffer[A]] = FieldEncoder.deriveFromTraversable
Nested fields
Protocol buffers allows to use the previously defined message as a field type.
protoless operates in the same way: it can encode/decode a field of type A
if there is an implicit instance of
Encoder[A]
/Decoder[A]
in the implicit scope.
Enumerations
Protobuf enumerations can be converted to Scala enumeration, with the constraint that enumerations values must be in the same order.
enum Color {
BLACK = 0;
WHITE = 1;
GREEN = 2;
}
case object Colors extends Enumeration {
type Color = Value
val Black, White, Green = Value
}
An implicit instance for the encoder
will be provided by protoless.
The decoder
must be explicitly derived with:
implicit val colorDecoder: RepeatableFieldDecoder[Colors.Value] = FieldDecoder.decodeEnum(Colors)
Value Classes
Implicit instances of Value Classes are provided if the underlying type has an encoder/decoder instance.
case class Money(val value: BigDecimal) extends AnyVal
// defined class Money
FieldEncoder[Money].encodeAsBytes(1, Money(BigDecimal(Long.MaxValue))) // I'm rich!
// res1: Array[Byte] = Array(10, 19, 57, 50, 50, 51, 51, 55, 50, 48, 51, 54, 56, 53, 52, 55, 55, 53, 56, 48, 55)
Other Scala types
Implicit instances are provided for the following scala types:
Scala type | Proto representation | Notes |
---|---|---|
BigDecimal | string | |
BigInt | string | |
UUID | repeated sint64 | store 128 bits UUID in two 64 bits long |
Short | int32 | |
Char | int32 |
Custom field encoder & decoder
You can create custom encoders/decoders by relying on existing ones, and add your own logic inside them.
case class Fahrenheit(value: Float)
implicit val fahrenheitFromCelciusDecoder = FieldDecoder[Float].map(celcius => Fahrenheit((celcius * 1.8f) + 32))
implicit val fahrenheitToCelciusEncoder = FieldEncoder[Float].contramap[Fahrenheit](f => (f.value - 32) / 1.8f)