In this blog post, I will explain how to convert a regular Java class
that contains a Protobuf message field to JSON using the Jackson
library, for example, in a Spring Boot application as a return value
of an @Controller
method.
You might wonder, how this is such a big deal? After all, you can create complex POJO hierarchies, and Jackson will pick them up just fine. Well, maybe this error message will convince you.
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [
Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException:
Type definition error: [simple type, class com.google.protobuf.UnknownFieldSet];
nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle
(through reference chain: com.thecodeslinger.ppjs.web.dto.AwesomeDto["awesomePowerUp"]
->com.thecodeslinger.ppjs.proto.AwesomePowerUpOuterClass$AwesomePowerUp["unknownFields"]
->com.google.protobuf.UnknownFieldSet["defaultInstanceForType"])] with root cause
Java classes that you create with the Protobuf compiler require their
JSON converter JsonFormat.Printer. So, how can we get Jackson
and JsonFormat.Printer
love each other and have a wedding together?
Simple: we create a custom JsonSerializer.
public class ProtobufSerializer extends JsonSerializer<Message> {
private final JsonFormat.Printer protobufJsonPrinter = JsonFormat.printer();
@Override
public void serialize(Message anyProtobufMessage, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {
// The magic sauce: use the Protobuf JSON converter to write a raw JSON
// string of the Protobuf message instance.
jsonGenerator.writeRawValue(protobufJsonPrinter.print(anyProtobufMessage));
}
}
This class is very simple and can work with any Protobuf Message class instance. This way, it is universal and only needs to be written once. The main ingredient is the jsonGenerator.writeRawValue method that takes the input without modification. Since we already ensure a proper JSON format using Protobuf’s converter, this is no problem in this case. Otherwise, be careful with this method.
The last step is to annotate the Message field in the POJO, so Jackson knows what to do.
@JsonSerialize(using = ProtobufSerializer.class)
private final AwesomePowerUpOuterClass.AwesomePowerUp awesomePowerUp;
You can find a complete working example that uses Spring Boot and a REST endpoint on my Github account.
wonderful! great post!
Is there also a generic Message deserializer for protobuf fields in a POJO?
An example would be great! 🙂
LikeLike
Brilliant! Thanks a lot!
LikeLike