Null support in Protobuf

For future students of the course "Java Developer. Professional" prepared a translation of the material.



We also invite you to attend an open webinar on the topic
"gRPC for microservices or non-REST single" . At an open webinar, we'll see what gRPC is and how it can be used (or can it?) Instead of REST for communication between microservices.






Protocol Buffer / gRPC:





service MyDataService {
  rpc UpateMyData (UpdateMyDataRequest) 
     returns (UpdateMyDataResponse);
}
message MyData {
  int32 id = 1;
  string stringValue = 2;
  SubData subData = 3;
}

message SubData {
 int64 bigValue = 1;
}
message UpdateMyDataRequest {
  MyData update = 1;
}
      
      



, MyData.stringValue



. , , - :





UpdateMyDataRequest request = UpdateMyDataRequest.newBuilder()
  .setUpdate(MyData.newBuilder()
    .setId(id)
    .setStringValue(null)
  )

serviceFutureStub.update(request)
      
      



NullPointerException







, protoc



, null



NullPointerException



. , null



. , get



.





UpdateMyDataRequest.newBuilder().build().getStringValue() == ""
      
      



null Protocol Buffers?

.





null?





, null :





  • Null β€” null.





  • Null β€” / .





  • Null β€” .





  • Null β€” .





, protobuf null. protobuf , Protobuf / gRPC API.





null , Protobuf.





proto3. proto2 , .





proto3

:









  1. null





  2. (0, . .) 





Null β€” Null ( OneOf NullValue)

null . , null . , MyData.stringValue



null.





Json- MyData :





{
  "id": 123
  "stringValue": null
}
      
      



, null. , null - . , nullable-. , Kotlin, .





Protobuf:





syntax = "proto3";

package io.github.efenglu.protobuf.examples.oneof;

option java_multiple_files = true;

import "google/protobuf/struct.proto";

service MyDataService {
  rpc UpateMyData (UpdateMyDataRequest) 
     returns (UpdateMyDataResponse);
}

message MyData {
  int32 intValue = 1;
  NullableString stringValue = 2;
  NullableSubData subData = 3;
}

message SubData {
  int64 bigValue = 1;
}

message NullableSubData {
  oneof kind {
    google.protobuf.NullValue null = 1;
    SubData data = 2;
  }
}

message NullableString {
  oneof kind {
    google.protobuf.NullValue null = 1;
    string data = 2;
  }
}

message UpdateMyDataRequest {
 MyData data = 1;
}


message UpdateMyDataResponse {

}
      
      



β€œnullable” :





  • NullableString







  • NullableSubData







oneof :





  • Null





  • null





Oneof



, null null.





Java- :





null:

UpdateMyDataRequest request = UpdateMyDataRequest.newBuilder()
  .setData(MyData.newBuilder()
    .setStringValue(NullableString.newBuilder()
      .setNull(NullValue.NULL_VALUE)
      .build()
    )
    .setSubData(NullableSubData.newBuilder()
      .setNull(NullValue.NULL_VALUE)
      .build()
    ).build()
).build();

service.upateMyData(request);
      
      



, null setNull



.





null :

UpdateMyDataRequest request = UpdateMyDataRequest.newBuilder()
  .setData(MyData.newBuilder()
    .setStringValue(NullableString.newBuilder()
      .setData("hello")
      .build()
    )
    .setSubData(NullableSubData.newBuilder()
      .setData(SubData.newBuilder()
        .setBigValue(1234567)
      .build()
    ).build()
  ).build()
).build();

service.upateMyData(request);
      
      



, setData



.





:

if (request.hasData()) {

  if (request.getData().hasStringValue()) {
    final String nullableString;
    if (request.getData().getStringValue().hasNull()) {
      nullableString = null;
    } else {
      nullableString = request.getData()
        .getStringValue()
        .getData();
      }
  }

  if (request.getData().hasSubData()) {
    final SubData nullableSubData;
    if (request.getData().getSubData().hasNull()) {
      nullableSubData = null;
    } else {
      nullableSubData = request.getData()
       .getSubData()
       .getData();
    }
  }

}
      
      



, null ( null), .





:





  • , null, , null.





  • null





:





  • null









Null : FieldMask

, / , .





null , . , null , , null, . json-.





{
 "id": 123
 -- ommited "stringValue" --
}
      
      



proto, , .





Protobuf:

service MyDataService {
  rpc Update (UpdateMyDataRequest) returns (UpdateMyDataResponse);
  rpc List (ListMyDataRequest) returns (ListMyDataResponse);
}

message MyData {
  int32 id = 1;
  string stringValue = 2;
  SubData subData = 3;
}

message SubData {
  int64 bigValue = 1;
}

message UpdateMyDataRequest {
  MyData update = 1;
  google.protobuf.FieldMask field_mask = 2;
}

message UpdateMyDataResponse {
  MyData new_data = 1;
}

message ListMyDataRequest {
  int32 id = 1;
  google.protobuf.FieldMask field_mask = 2;
}

message ListMyDataResponse {
  repeated MyData data = 1;
}
      
      



, UpdateMyDataRequest



ListMyDataRequest



FieldMask



. , , .





:





MyData sendUpdate(int id, String value) {
  UpdateMyDataRequest request = UpdateMyDataRequest.newBuilder()
    .setUpdate(MyData.newBuilder()
      .setId(id)
      .setStringValue(value)
    )
    .setFieldMask(FieldMaskUtil.fromFieldNumbers(
      MyData.class, 
      MyData.STRINGVALUE_FIELD_NUMBER)
    )
    .build();

  return serviceFutureStub.update(request).getNewData();
}

List<MyData> listOnlySubData(int id) {
  ListMyDataRequest request = ListMyDataRequest.newBuilder()
    .setId(id)
    .setFieldMask(FieldMaskUtil.fromFieldNumbers(
      MyData.class, 
      MyData.SUBDATA_FIELD_NUMBER)
    )
    .build();

  return serviceFutureStub.list(request).getDataList();
}
      
      



:





@Override
public void update(
  UpdateMyDataRequest request,
  StreamObserver<UpdateMyDataResponse> responseObserver
) {

  MyData updateData = request.getUpdate();
  FieldMask fieldMask = request.getFieldMask();

  // Fetch exiting Values
  MyData existing = repo.readData(updateData.getId());
  MyData.Builder builder = existing.toBuilder();

  // Update only the fields listed in the fieldmask
  FieldMaskUtil.merge(fieldMask, updateData, builder);

  // Store the result
  repo.writeData(builder.build());

  // Send the new state back
  responseObserver.onNext(UpdateMyDataResponse.newBuilder()
    .setNewData(builder)
    .build()
  );
}
      
      



update:





  1. , .





  2. .





  3. FieldMaskUtil



    .





  4. .





  5. .





FieldMaskUtil



, , , .





@Override
public void list(
  ListMyDataRequest request,
  StreamObserver<ListMyDataResponse> responseObserver
) {
  int id = request.getId();
  FieldMask fieldMask = request.getFieldMask();
  // Fetch the list
  List<MyData> result = repo.listData(id);

  ListMyDataResponse.Builder response = 
    ListMyDataRespons.newBuilder();
  MyData.Builder builder = MyData.newBuilder();
  for (MyData data : result) {
    builder.clear();

    // Use the field mask to send back ONLY the data requested
    FieldMaskUtil.merge(fieldMask, data, builder);

    response.addData(builder);
  }

  // Send the filtered list back
  responseObserver.onNext(response.build());
}
      
      



, .





  1. .





  2. , .





  3. .





:





  1. .





  2. .





:





  1. FieldMask



    .





  2. , .





  3. .





Null : Has

"has



", boolean. (β€œhas been set



”), true



. , , . , , .





, , Message. , Proto3 (wrapper) .





...

import "google/protobuf/wrappers.proto";

service MyDataService {
  rpc Update (UpdateMyDataRequest) returns (UpdateMyDataResponse);
}
...
message UpdateMyDataRequest {
  int32 id = 1;
  google.protobuf.StringValue stringValue = 2;
  UpdateSubData subData = 3;
}

message UpdateSubData {
  google.protobuf.Int64Value bigValue = 1;
}
...
      
      



google/protobuf/wrappers.proto



google.protobuf.StringValue



google.protobuf.Int64Value



. , "has



".





:

void update() {
  service.update(UpdateMyDataRequest.newBuilder()
    .setStringValue(StringValue.of("customValue"))
    .build()
  );
}
      
      



, . β€” StringValue



.





, (pre-auto boxing) Java, .





:

@Override
public void update(
  UpdateMyDataRequest request,
  StreamObserver<UpdateMyDataResponse> responseObserver) {

  // Fetch exiting Values
  MyData existing = repo.readData(request.getId());
  MyData.Builder builder = existing.toBuilder();

  // Update Fields as necessary
  if (request.hasStringValue()) {
    builder.setStringValue(request.getStringValue().getValue());
  }

  if (request.hasSubData()) {
    if (request.getSubData().hasBigValue()) {
      builder.setSubData(
        builder.getSubData().toBuilder()
          .setBigValue(request.getSubData()
            .getBigValue()
            .getValue()
          )
        );
      }
  }

  repo.writeData(builder.build());

  responseObserver.onNext(UpdateMyDataResponse.newBuilder()
    .setNewData(builder)
    .build()
  );
}
      
      



, FieldMaskUtil



, .





  1. .





  2. .





  3. has



    .





  4. .





  5. .





:





  1. .





  2. .





:





  1. : has



    , .





  2. .





Null :

, "" , . , !





.





: Β« , null.Β»





!





  • .





  • Proto3 , (0, "") , .





, .





"has



" . "has



" .





Null : Null-

:





String value;
if (value != null) {
  // insert value into database
}
      
      



- ?





  • ""



    ?





  • value " "



    ?





Protobuf , null. , null, , apache commons. 





String value;
if (StringUtils.isNotBlank(value)) {
  // insert value into database
}
      
      



, , value



.





" null" protoc .





:





  • Optional





protoc :





  • null get.





  • null .





  • "has



    " .





protoc How to customize the gRPC generated code.





? , Protocol Buffers null. .





Protocol buffers :





  • ?





  • null?





  • null?





Github. 






"Java Developer. Professional".





Β«gRPC REST- Β».








All Articles