How to Mask certain fields in Protobuf

11.7k Views Asked by At

I couldnt find a way to mask certain fields in protobuf structure. I did read about the FieldMaskUtil and tried few examples but it seems to do the reverse i.e copy fields which are mentioned in FieldMask which is different from what i wanted. Here's the sample structure and corresponding Test code.

Proto:

syntax = "proto3";

package model;

option java_package = "test.demo.services.protobuf.customer.model";
option java_outer_classname = "CustomerProto";

message Accounts {
  repeated Account account = 1;
}

message Account {

  int32 id = 1;
  string number = 2;
  int32 customer_id = 3;

}

 message Customers {
   repeated Customer customers = 1;
}

message Customer {

  int32 id = 1;
  string pesel = 2;
  string name = 3;
  CustomerType type = 4;
  repeated Account accounts = 5;

  enum CustomerType {
    INDIVIDUAL = 0;
    COMPANY = 1;
  }

}

Here's sample test code

package test.demo.services.protobuf.customer.model;

import org.junit.Test;
import test.demo.services.protobuf.customer.model.CustomerProto.Customer;
import com.google.protobuf.util.FieldMaskUtil;


public class TestMerge {


  @Test
  public void eraseFields() {

        Customer request = Customer.newBuilder().setId(10).setPesel("12345").setName("Harry Alto").build();
    // Erase name
      Customer.Builder modifieldRequest = Customer.newBuilder();
      FieldMaskUtil.merge(FieldMaskUtil.fromString("name"), request, modifieldRequest);
      System.out.println( modifieldRequest.build().toString());
}

}

Here's the output:

name: "Harry Alto"

What i would have expected is to print everything other than name

id: 10
pesel: "12345"

Is there a way to do what i want

2

There are 2 best solutions below

3
On BEST ANSWER
FieldMaskUtil.merge(FieldMaskUtil.fromString("name"), request, modifieldRequest);

What i would have expected is to print everything other than name

No, according to the JavaDocs for FieldMask, the behavior is opposite of what you described:

Field masks are used to specify a subset of fields that should be returned by a get operation or modified by an update operation.

The mask acts as as a set intersection operation, selecting only fields that were specified. In your case, the mask specifies only "name", so that is what it selects.

The behavior you are seeking is really a set complement operation, selecting all fields that were not specified. I'm not aware of a built-in method in the Protobuf API to do this. Options for implementing this yourself are:

  • Construct multiple FieldMask instances using a separate call to FieldMask#fromString for each named field that you want to retain. Then, use FieldMaskUtil#union to combine all of them. This will retain only the specifically listed fields. If you evolve the schema later to add more fields, then it won't pick up those new fields. You'd have to change the code and add the new named fields into that union.
  • Call MessageOrBuilder#getAllFields to get a descriptor for all fields in the message. Iterate through all of them, using FieldMaskUtil#union as described above, but skip specific fields that you want to ignore, like "name". If you evolve the schema later to add more fields, then this will pick up those new fields. You won't need to change the code to start including those new fields.

Note the differences with respect to schema evolution in those solutions. The choice of which one is most appropriate depends on your requirements.

1
On

If you truly wish to erase the name field you would do it this way:

Customer.Builder request Customer.newBuilder()
    .setId(10).setPesel("12345").setName("Harry Alto").build();
// Erase name
Customer.Builder modifiedRequest = request.toBuilder();
Customer modifyRequest = Customer.getDefaultInstance();
FieldMaskUtil.merge(FieldMaskUtil.fromString("name"), modifyRequest, modifiedRequest);
System.out.println(modifiedRequest.build().toString());

That should produce your desired result.