Hi there,
I'm hitting a wall while attempting to use GSON to serialize a nested set of Protobuf GeneratedMessage objects.
Looking at the rough example at
https://code.google.com/p/google-gson/source/browse/trunk/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java?r=613 I've managed to implement a nearly complete JsonSerializer<GeneratedMessage> that supports the serialization of Protobuf primitives, enums, and repeated values.
However, I can't figure out how to tweak my JsonSerializer to support nested Protobuf messages. For instance:
message User {
required String userId = 1;
required String name = 2;
}
message Session {
required String sessionId = 1;
required User user = 2; // "Nested".. references message User above
}When compiled using 'protoc', these result in the following Java classes (highly distilled):
import com.google.protobuf.GeneratedMessage;
public final class User extends GeneratedMessage {
private String userId;
private String name;
}
public final class Session extends GeneratedMessage {
private String sessionId;
private User user;
}I want to write a generic serializer for "GeneratedMessage". Ignoring deserialization for the moment, my JsonSerializer<GeneratedMessage> looks something like this:
public final class GeneratedMessageAdapter implements JsonSerializer<GeneratedMessage> {
@Override
public JsonElement serialize(GeneratedMessage src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject ret = new JsonObject();
Map<Descriptors.FieldDescriptor, Object> fields = src.getAllFields();
for (Map.Entry<Descriptors.FieldDescriptor, Object> fieldPair : fields.entrySet()) {
final Descriptors.FieldDescriptor desc = fieldPair.getKey();
final Object value = fieldPair.getValue();
if (desc.isRepeated()) {
// Handle Protobuf "repeated" value as a JsonArray
ret.add(desc.getName(), new JsonArray(...));
} else if (desc.getJavaType().equals(JavaType.ENUM)) {
// Handle Protobuf "enum" value as a JsonPrimitive
ret.add(desc.getName(), new JsonPrimitive(...));
} else if (desc.getJavaType().equals(JavaType.MESSAGE)) {
// Nested Protobuf GeneratedMessage
// ***Need to somehow re-invoke my GeneratedMessageAdapter to serialize the nested object??***
} else {
// Handle Protobuf primatives (everything else)
ret.add(desc.getName(), context.serialize(value));
}
}
return ret;
}
}And, I'm registering my adapter using the following:
GsonBuilder builder = GsonBuilder().serializeNulls();
builder.registerTypeAdapter(GeneratedMessage.class, new GeneratedMessageAdapter());It seems my problem is in the
else if (desc.getJavaType().equals(JavaType.MESSAGE)) conditional above.
Calling
context.serialize(value) on this nested GeneratedMessage object seems to invoke the default object serializer, and not my GeneratedMessage registered type adapter. As such, the serialized JSON for any nested GeneratedMessage objects contains Protobuf internal fields rendered in a way that's unusable to my application in the browser.
Further, explicitly calling
context.serialize(value, GeneratedMessage.class)to force the issue results in a classic StackOverflowError (expected, according to documentation but I tried).
Lastly, I tried implementing my own
new JsonSerializationContext(){...} inline and invoking .serialize on that using a new Gson instance just for this one case... thinking I was clever enough, but no dice.
In the end, pretty sure this problem isn't specific to Protobuf generated classes. I conjecture anyone would have the same problem with these, trying to write a generic serializer for Bar:
public abstract class Bar {}
public class Foo extends Bar {}
public class Baz extends Bar {
private Foo foo;
}If anyone out there has advice or thoughts on how to address this, I would be forever grateful. Thanks!
-Mark