I have a simple proto like this (I cannot edit this file, please don't ask why):
syntax = "proto3";
package abc;
message Status {
StatusCodeType statusCode = 1;
enum StatusCodeType {
SUCCESS = 0;
ERROR = 1;
}
}
I use a maven plugin to compile it to java class:
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf.maven.plugin.version}</version>
<configuration>
<protoSourceRoot>${project.basedir}/src/main/resources/protobuf</protoSourceRoot>
<protocExecutable>protoc</protocExecutable>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin
When I tried to serialize the Status object that have the StatusCodeType.SUCCESS
, I received empty byte array:
public static void main(String[] args) {
StatusOuterClass.Status s = StatusOuterClass.Status.newBuilder()
.setStatusCode(StatusOuterClass.Status.StatusCodeType.SUCCESS)
.build();
System.out.println(s.toByteArray().length); //PRINT 0
}
I dig into the generated code and I find this logic:
@java.lang.Override
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
if (statusCode_ != abc.StatusOuterClass.Status.StatusCodeType.SUCCESS.getNumber()) {
output.writeEnum(1, statusCode_);
}
getUnknownFields().writeTo(output);
}
Can see that it will only write to the byte array if statusCode_ != abc.StatusOuterClass.Status.StatusCodeType.SUCCESS.getNumber()
I think it is a feature of protobuf to optimize payload size, but it cause a problem in my project. If I invoke an API written by Spring boot and send this empty byte array as payload, then the controller will reject as 400 bad request.
Of course I can edit the controller method and make the payload optional, but it is not the proper way. For ex, how can we know whether client send success payload or didn't send anything (because in both case, the payload is null)? Is there any solution?
PS: I have contacted with some python/C++ guys and they said that their code will produce an array of {8, 0} instead of empty. How is it possible?
An empty byte array is a correct encoding.
If you cannot modify the controller to accept 0-byte messages, you can make use of the concatenation property of protocol buffers by emitting {8, 0}
sequence before or instead of your actual protobuf. Since the last value for a specific field wins, this will have no effect beyond always sending those two bytes.
In your situation it can be as simple as:
byte[] res = StatusOuterClass.Status.newBuilder()
.setStatusCode(StatusOuterClass.Status.StatusCodeType.SUCCESS)
.build();
if (res.length == 0) {
res = new byte[] { 8, 0 };
}
The "before" case could be something like:
var baos = new ByteArrayOutputStream();
baos.writeBytes(new byte[] { 8, 0 });
baos.writeBytes(StatusOuterClass.Status.....build());
byte[] res = baos.toByteArray();