From 6feb325044240ba6ff15aceee2b3b091fa278d4f Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Wed, 27 Oct 2010 23:06:26 +0000 Subject: [PATCH] Added a type adapter for serializing and deserializing protobufs --- .../gson/protobuf/ProtoTypeAdapter.java | 158 ++++++++++++------ proto/src/main/protobuf/bag.proto | 2 +- .../google/gson/protobuf/FunctionalTest.java | 24 ++- 3 files changed, 125 insertions(+), 59 deletions(-) diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java index 1fbb0e34..891e0d25 100644 --- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java +++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java @@ -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 { +/** + * Gson type adapter for protocol buffers + * + * @author Inderjeet Singh + */ +public class ProtoTypeAdapter implements JsonSerializer, + JsonDeserializer { @Override - public JsonElement serialize(Message msg, Type typeOfMsg, JsonSerializationContext context) { - JsonObject obj = new JsonObject(); - Map allFields = msg.getAllFields(); - for (Map.Entry 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 fields = src.getAllFields(); + + for (Map.Entry 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 protoClass = + (Class) 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, Method> mapOfMethods = mapOfMapOfMethods.get(methodName); + if (mapOfMethods == null) { + mapOfMethods = new HashMap, 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, Method>> mapOfMapOfMethods = + new HashMap, Method>>(); +} \ No newline at end of file diff --git a/proto/src/main/protobuf/bag.proto b/proto/src/main/protobuf/bag.proto index c66b8b55..7c1c96cc 100644 --- a/proto/src/main/protobuf/bag.proto +++ b/proto/src/main/protobuf/bag.proto @@ -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; } diff --git a/proto/src/test/java/com/google/gson/protobuf/FunctionalTest.java b/proto/src/test/java/com/google/gson/protobuf/FunctionalTest.java index 692c7185..31724509 100644 --- a/proto/src/test/java/com/google/gson/protobuf/FunctionalTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/FunctionalTest.java @@ -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()); + } }