Added a type adapter for serializing and deserializing protobufs

This commit is contained in:
Inderjeet Singh 2010-10-27 23:06:26 +00:00
parent 9b10e70a79
commit 6feb325044
3 changed files with 125 additions and 59 deletions

View File

@ -1,69 +1,125 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.gson.protobuf;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
import com.google.protobuf.Message;
import com.google.protobuf.GeneratedMessage;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ProtoTypeAdapter implements JsonSerializer<Message> {
/**
* Gson type adapter for protocol buffers
*
* @author Inderjeet Singh
*/
public class ProtoTypeAdapter implements JsonSerializer<GeneratedMessage>,
JsonDeserializer<GeneratedMessage> {
@Override
public JsonElement serialize(Message msg, Type typeOfMsg, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
Map<FieldDescriptor, Object> allFields = msg.getAllFields();
for (Map.Entry<FieldDescriptor, Object> entry : allFields.entrySet()) {
FieldDescriptor key = entry.getKey();
Object value = entry.getValue();
JavaType javaType = key.getJavaType();
Class<?> type = toJavaType(javaType);
JsonElement element = context.serialize(value, type);
obj.add(key.getName(), element);
public JsonElement serialize(GeneratedMessage src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject ret = new JsonObject();
final Map<FieldDescriptor, Object> fields = src.getAllFields();
for (Map.Entry<FieldDescriptor, Object> fieldPair : fields.entrySet()) {
final FieldDescriptor desc = fieldPair.getKey();
if (desc.isRepeated()) {
List<?> fieldList = (List<?>) fieldPair.getValue();
if (fieldList.size() != 0) {
JsonArray array = new JsonArray();
for (Object o : fieldList) {
array.add(context.serialize(o));
}
ret.add(desc.getName(), array);
}
} else {
ret.add(desc.getName(), context.serialize(fieldPair.getValue()));
}
}
return obj;
return ret;
}
private Class<?> toJavaType(JavaType javaType) {
switch (javaType) {
case BOOLEAN:
return Boolean.class;
case BYTE_STRING:
return String.class;
case DOUBLE:
return double.class;
case ENUM:
return Enum.class;
case FLOAT:
return float.class;
case INT:
return int.class;
case LONG:
return long.class;
case MESSAGE:
return Message.class;
case STRING:
return String.class;
@SuppressWarnings("unchecked")
@Override
public GeneratedMessage deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
try {
JsonObject jsonObject = json.getAsJsonObject();
Class<? extends GeneratedMessage> protoClass =
(Class<? extends GeneratedMessage>) typeOfT;
try {
// Invoke the ProtoClass.newBuilder() method
Object protoBuilder = getCachedMethod(protoClass, "newBuilder")
.invoke(null);
Class<?> builderClass = protoBuilder.getClass();
Descriptor protoDescriptor = (Descriptor) getCachedMethod(
protoClass, "getDescriptor").invoke(null);
// Call setters on all of the available fields
for (FieldDescriptor fieldDescriptor : protoDescriptor.getFields()) {
String name = fieldDescriptor.getName();
if (jsonObject.has(name)) {
JsonElement jsonElement = jsonObject.get(name);
String fieldName = name + "_";
Field field = protoClass.getDeclaredField(fieldName);
Type fieldType = field.getGenericType();
Object fieldValue = context.deserialize(jsonElement, fieldType);
Method method = getCachedMethod(
builderClass, "setField", FieldDescriptor.class, Object.class);
method.invoke(protoBuilder, fieldDescriptor, fieldValue);
}
}
// Invoke the build method to return the final proto
return (GeneratedMessage) getCachedMethod(builderClass, "build")
.invoke(protoBuilder);
} catch (SecurityException e) {
throw new JsonParseException(e);
} catch (NoSuchMethodException e) {
throw new JsonParseException(e);
} catch (IllegalArgumentException e) {
throw new JsonParseException(e);
} catch (IllegalAccessException e) {
throw new JsonParseException(e);
} catch (InvocationTargetException e) {
throw new JsonParseException(e);
}
} catch (Exception e) {
throw new JsonParseException("Error while parsing proto: ", e);
}
return Object.class;
}
}
private static Method getCachedMethod(Class<?> clazz, String methodName,
Class<?>... methodParamTypes) throws NoSuchMethodException {
Map<Class<?>, Method> mapOfMethods = mapOfMapOfMethods.get(methodName);
if (mapOfMethods == null) {
mapOfMethods = new HashMap<Class<?>, Method>();
mapOfMapOfMethods.put(methodName, mapOfMethods);
}
Method method = mapOfMethods.get(clazz);
if (method == null) {
method = clazz.getMethod(methodName, methodParamTypes);
mapOfMethods.put(clazz, method);
}
return method;
}
private static Map<String, Map<Class<?>, Method>> mapOfMapOfMethods =
new HashMap<String, Map<Class<?>, Method>>();
}

View File

@ -19,5 +19,5 @@ option java_package = "com.google.gson.protobuf.generated";
message SimpleProto {
optional string msg = 1;
optional int64 count = 2;
optional int32 count = 2;
}

View File

@ -18,8 +18,8 @@ package com.google.gson.protobuf;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.protobuf.generated.Bag.SimpleProto;
import com.google.protobuf.Message;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.GeneratedMessage;
import junit.framework.TestCase;
@ -29,9 +29,8 @@ public class FunctionalTest extends TestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
gson = new GsonBuilder()
// .registerTypeHierarchyAdapter(Message.class, new ProtoTypeAdapter())
.create();
gson = new GsonBuilder().registerTypeHierarchyAdapter(
GeneratedMessage.class, new ProtoTypeAdapter()).create();
}
public void testSerializeEmptyProto() {
@ -40,15 +39,26 @@ public class FunctionalTest extends TestCase {
assertEquals("{}", json);
}
public void testDeserializeEmptyProto() {
SimpleProto proto = gson.fromJson("{}", SimpleProto.class);
assertFalse(proto.hasCount());
assertFalse(proto.hasMsg());
}
public void testSerializeProto() {
Descriptor descriptor = SimpleProto.getDescriptor();
SimpleProto proto = SimpleProto.newBuilder()
.setField(descriptor.findFieldByNumber(1), "foo")
.setField(descriptor.findFieldByNumber(2), 3)
.setField(descriptor.findFieldByName("count"), 3)
.setField(descriptor.findFieldByName("msg"), "foo")
.build();
String json = gson.toJson(proto);
System.out.println(json);
assertTrue(json.contains("\"msg\":\"foo\""));
assertTrue(json.contains("\"count\":3"));
}
public void testDeserializeProto() {
SimpleProto proto = gson.fromJson("{msg:'foo',count:3}", SimpleProto.class);
assertEquals("foo", proto.getMsg());
assertEquals(3, proto.getCount());
}
}