Skip to content

A conversion tool between scala types and protobuf-java types.

License

Notifications You must be signed in to change notification settings

changvvb/scala-protobuf-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

4bd5be5 · Dec 21, 2021

History

94 Commits
Dec 20, 2021
Dec 18, 2021
Dec 20, 2021
Nov 2, 2020
Dec 17, 2021
Dec 21, 2021
Dec 21, 2021
Dec 20, 2021
Dec 20, 2021
Dec 20, 2021

Repository files navigation

scala-protobuf-java

Scala CI codecov Maven Central

News

scala-protobuf-java has supported scala 3.

What is this

scala-protobuf-java is a conversion tool between scala types and protobuf-java types. It can help impatient you save a lot of codes. scala-protobuf-java use scala macro to generate what you need, so it's type safe at compile time. Now you just keep free to write your code, scala-proto-java will check the error at compile time.

Import to your project

sbt (for 2.12, 2.13 and scala 3. If you need more, just submit an issue or PR)

libraryDependencies += "com.github.changvvb" %% "scala-protobuf-java" % "0.3.0"

maven

<dependency>
  <groupId>com.github.changvvb</groupId>
  <artifactId>scala-protobuf-java_2.13</artifactId>
  <version>0.3.0</version>
</dependency>

How to use

Basic usage

Define case class Person

case class Person(
  id: Long,
  name: String,
  phone: Option[String],
  hobbies: Seq[String])

Define protobuf message PBPerson

message PBPerson {
    int64 id = 1;
    string name = 2;
    google.protobuf.StringValue phone = 3;
    repeated string hobbies = 4;
}

Using protoc, it will generate PBPerson.java with corresponding members. You can convert Person to PBPerson like this

val builder = PBPerson.newBuilder()
builder.setId(person.id)
builder.setName(person.name)
person.phone.foreach(p => builder.setPhone(StringValue.of(p)))
person.hobbies.foreach(builder.addHobbies)
val pbPerson:PBPerson = builder.build()

On the contrary, you can convert PBPerson to Person like this

val person1 = Person(
  pbPerson.getId,
  pbPerson.getName,
  if(pbPerson.hasPhone) Some(pbPerson.getPhone.getValue) else None,
  scala.collection.JavaConverters.iterableAsScalaIterable(pbPerson.getHobbiesList).toSeq
)

Now, we can do it with a few codes with the help of scala-protobuf-java

import pbconverts.{ Protoable, Scalable }
val convertedPBPerson:PBPerson = Protoable[Person,PBPerson].toProto(person)
val convertedPerson:Person = Scalable[Person,PBPerson].toScala(pbPerson)

More simplified

import pbconverts.ProtoScalable
val protoScalable = ProtoScalble[Person,PBPerson]
protoScalable.toProto(person)
protoScalable.toScala(pbPerson)

Or you can use implicit style

import pbconverts.{ ProtoScalable, Converter}
implicit val protoScalable = ProtoScalble[Person,PBPerson]
Converter.toProto(person)

In general, we often put the implicit value in companion object

object Person {
  implicit val protoScalable = ProtoScalble[Person,PBPerson]
}
Converter.toScala(pbPerson)

Nested structure

Define a nested case class ParentMessage

case class SubMessage(subValue:Int)
case class ParentMessage(parentValue:Int, subMessage:SubMessage)

Define a nested protobuf message PBParentMessage

message PBSubMessage {
  int32 sub_value = 1;
}

message PBParentMessage {
  int32 parent_value = 1;
  PBSubMessage sub_message = 2;
}
import pbconverts.{ Scalable, Protoable }
implicit val subProtoable = Protoable[SubMessage,PBSubMessage]
Protoable[ParentMessage, PBParentMessage].toProto(ParentMessage(...))


implicit val subScalable = Scalable[SubMessage,PBSubMessage]
Scalable[ParentMessage, PBParentMessage].toScala(PBParentMessage.newBuilder().build())

Hint: you can replace Protoable[SubMessage,PBSubMessage] and Scalable[SubMessage,PBSubMessage] with ProtoScalable[SubMessage,PBSubMessage]

Custom your own conversion

If you want use Protoable[Person, PBPerson] to convert a Person object to PBPerson object but you want custom field id. You can use ProtoableBuilder:

val customProtoable = ProtoableBuilder[Person,PBPerson]
                        .setField(_.getId, p => if (p.id < 0) 0 else p.id)
                        .build
customProtoable.toProto(Person(...))

Also, if you want convert protobuf to case class with the same logic, you can use ScalableBuilder

val customScalable = ScalableBuilder[Person,PBPerson]
                       .setField(_.id, p => if(p.getId < 0) 0 else p.id)
                       .build
customScalable.toScala(PBPerson.newBuilder().build)