Handling null values in protobuffers
There's no easy solution to this. I'd recommend just dealing with the null checks. But if you really want to get rid of them, here are a couple ideas:
- You could write a code generator plugin which adds
setOrClearFoo()
methods to each Java class. The Java code generator provides insertion points for this (see the end of that page). - You could use Java reflection to iterate over the
get*()
methods ofp
, call each one, check fornull
, and then call theset*()
method ofbuilder
if non-null. This will have the added advantage that you won't have to update your copy code every time you add a new field, but it will be much slower than writing code that copies each field explicitly.
Disclaimer: Answer from a Googler using protobufs on a daily basis. I'm by no means representing Google in any way.
- Name your proto
Person
instead ofPersonProto
orProtoPerson
. Compiled protobufs are just class definitions specified by the language you are using, with some improvements. Adding "Proto" is extra verbosity. - Use
YourMessage.hasYourField()
instead ofYourMessage.getYourField() != null
. Default value for protobuf string is an empty string, which does NOT equal to null. Whereas, no matter whether your field is unset or cleared or empty string,.hasYourField()
always returns false. See default values for common protobuf field types. - You've probably known, but I wanna say explicitly: Don't programmatically set a protobuf field to
null
. Even for outside of protobuf,null
causes all sorts of problems. Use.clearYourField()
instead. Person.Builder
class does NOT have a.newBuilder()
method.Person
class does. Understand the Builder Pattern like this: You create a new builder only if you do not have it yet.
A rewrite of your protobuf:
message Person {
optional string first_name = 1;
optional string last_name = 2;
optional string address_1 = 3;
}
A rewrite of your logic:
Person thatPerson = Person.newBuilder()
.setFirstName("Aaa")
.setLastName("Bbb")
.setAddress1("Ccc")
.build();
Person.Builder thisPersonBuilder = Person.newBuilder()
if (thatPerson.hasFirstName()) {
thisPersonBuilder.setFirstName(thatPerson.getFirstName());
}
if (thatPerson.hasLastName()) {
thisPersonBuilder.setLastName(thatPerson.getLastName());
}
if (thatPerson.hasAddress1()) {
thisPersonBuilder.setAddress1(thatPerson.getAddress1());
}
Person thisPerson = thisPersonBuilder.build();
And if thatPerson
is a person object that you created that has attribute values that could be an empty string, empty spaces or null, then I'd recommend using Guava's Strings
library:
import static com.google.common.base.Strings.nullToEmpty;
Person.Builder thisPersonBuilder = Person.newBuilder()
if (!nullToEmpty(thatPerson.getFirstName()).trim().isEmpty()) {
thisPersonBuilder.setFirstName(thatPerson.getFirstName());
}
if (!nullToEmpty(thatPerson.hasLastName()).trim().isEmpty()) {
thisPersonBuilder.setLastName(thatPerson.getLastName());
}
if (!nullToEmpty(thatPerson.hasAddress1()).trim().isEmpty()) {
thisPersonBuilder.setAddress1(thatPerson.getAddress1());
}
Person thisPerson = thisPersonBuilder.build();
Proto 3
wrappers.proto supports nullable values:
- string(StringValue),
- int(Int32Value),
- bool(BoolValue)
- and etc
Example
syntax = "proto3";
import "google/protobuf/wrappers.proto";
message ProtoPerson {
google.protobuf.StringValue firstName = 1;
google.protobuf.StringValue lastName = 2;
google.protobuf.StringValue address1 = 3;
google.protobuf.Int32Value age = 4;
}