Added a type adapter for serializing and deserializing protobufs
This commit is contained in:
parent
9b10e70a79
commit
6feb325044
@ -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>>();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user