Refactor common class from gson code

The TypeAdapters in the gson-jackson code were nearly identical,
so a common class was factored out.  This also enabled junit tests
to attain 100% coverage on that code, whereas there were a couple
of branches that could not be tested previously.
Addressed new sonar issues.
Removed unused import.

Change-Id: Id8e6460c881c6ce0239768f182e4e652cd10645f
Issue-ID: POLICY-1428
Signed-off-by: Jim Hahn <jrh3@att.com>
diff --git a/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
index 66475e3..6906696 100644
--- a/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
+++ b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
@@ -31,7 +31,6 @@
 import java.nio.charset.StandardCharsets;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.MessageBodyReader;
@@ -117,7 +116,7 @@
     @Override
     public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
                     MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
-                    throws IOException, WebApplicationException {
+                    throws IOException {
 
         try (InputStreamReader streamReader = new InputStreamReader(entityStream, StandardCharsets.UTF_8)) {
             Type jsonType = (type.equals(genericType) ? type : genericType);
diff --git a/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java
index 67c5144..3458a59 100644
--- a/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java
+++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonFieldAdapterFactory.java
@@ -21,14 +21,9 @@
 package org.onap.policy.common.gson;
 
 import com.google.gson.Gson;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
 import com.google.gson.TypeAdapter;
 import com.google.gson.TypeAdapterFactory;
 import com.google.gson.reflect.TypeToken;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
@@ -36,6 +31,7 @@
 import org.onap.policy.common.gson.internal.Deserializer;
 import org.onap.policy.common.gson.internal.FieldDeserializer;
 import org.onap.policy.common.gson.internal.FieldSerializer;
+import org.onap.policy.common.gson.internal.JacksonTypeAdapter;
 import org.onap.policy.common.gson.internal.Serializer;
 
 /**
@@ -66,116 +62,40 @@
             return null;
         }
 
-        return new JacksonFieldAdapter<>(gson, data, gson.getDelegateAdapter(this, type));
+        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
+        List<Serializer> sers = makeSerializers(gson, data);
+        List<Deserializer> desers = makeDeserializers(gson, data);
+
+        return new JacksonTypeAdapter<>(gson, delegate, sers, desers);
     }
 
     /**
-     * Adapter for a single class.
+     * Creates a complete list of serializers.
      *
-     * @param <T> type of class on which the adapter works
+     * @param gson the associated gson object
+     * @param data data used to configure the serializers
+     * @return a list of all serializers
      */
-    private static class JacksonFieldAdapter<T> extends TypeAdapter<T> {
+    private List<Serializer> makeSerializers(Gson gson, ClassWalker data) {
+        List<Serializer> ser = new ArrayList<>();
 
-        /**
-         * Used to create an object of the given class.
-         */
-        private final TypeAdapter<T> delegate;
+        data.getOutProps(Field.class).forEach(field -> ser.add(new FieldSerializer(gson, field)));
 
-        /**
-         * Used to serialize/deserialize a JsonElement.
-         */
-        private final TypeAdapter<JsonElement> elementAdapter;
+        return ser;
+    }
 
-        /**
-         * Serializers for each item within the object.
-         */
-        private final Serializer[] serializers;
+    /**
+     * Creates a complete list of deserializers.
+     *
+     * @param gson the associated gson object
+     * @param data data used to configure the deserializers
+     * @return a list of all deserializers
+     */
+    private List<Deserializer> makeDeserializers(Gson gson, ClassWalker data) {
+        List<Deserializer> deser = new ArrayList<>();
 
-        /**
-         * Deserializers for each item within the object.
-         */
-        private final Deserializer[] deserializers;
+        data.getInProps(Field.class).forEach(field -> deser.add(new FieldDeserializer(gson, field)));
 
-        /**
-         * Constructs the object.
-         *
-         * @param gson the associated gson object
-         * @param data data used to configure the adapter
-         * @param delegate default constructor for the type
-         */
-        public JacksonFieldAdapter(Gson gson, ClassWalker data, TypeAdapter<T> delegate) {
-            this.delegate = delegate;
-
-            this.elementAdapter = gson.getAdapter(JsonElement.class);
-
-            // create serializers
-            this.serializers = makeSerializers(gson, data).toArray(new Serializer[0]);
-
-            // create deserializers
-            this.deserializers = makeDeserializers(gson, data).toArray(new Deserializer[0]);
-        }
-
-        /**
-         * Creates a complete list of serializers.
-         *
-         * @param gson the associated gson object
-         * @param data data used to configure the serializers
-         * @return a list of all serializers
-         */
-        private List<Serializer> makeSerializers(Gson gson, ClassWalker data) {
-            List<Serializer> ser = new ArrayList<Serializer>();
-
-            data.getOutProps(Field.class).forEach(field -> ser.add(new FieldSerializer(gson, field)));
-
-            return ser;
-        }
-
-        /**
-         * Creates a complete list of deserializers.
-         *
-         * @param gson the associated gson object
-         * @param data data used to configure the deserializers
-         * @return a list of all deserializers
-         */
-        private List<Deserializer> makeDeserializers(Gson gson, ClassWalker data) {
-            List<Deserializer> deser = new ArrayList<Deserializer>();
-
-            data.getInProps(Field.class).forEach(field -> deser.add(new FieldDeserializer(gson, field)));
-
-            return deser;
-        }
-
-        @Override
-        public void write(JsonWriter out, T value) throws IOException {
-            JsonElement tree = delegate.toJsonTree(value);
-
-            if (tree.isJsonObject()) {
-                JsonObject jsonObj = tree.getAsJsonObject();
-
-                // serialize each item from the value into the target tree
-                for (Serializer serializer : serializers) {
-                    serializer.addToTree(value, jsonObj);
-                }
-            }
-
-            elementAdapter.write(out, tree);
-        }
-
-        @Override
-        public T read(JsonReader in) throws IOException {
-            JsonElement tree = elementAdapter.read(in);
-            T object = delegate.fromJsonTree(tree);
-
-            if (tree.isJsonObject()) {
-                JsonObject jsonObj = tree.getAsJsonObject();
-
-                // deserialize each item from the tree into the target object
-                for (Deserializer dser : deserializers) {
-                    dser.getFromTree(jsonObj, object);
-                }
-            }
-
-            return object;
-        }
+        return deser;
     }
 }
diff --git a/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java
index 1c3039f..de96231 100644
--- a/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java
+++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonMethodAdapterFactory.java
@@ -21,14 +21,9 @@
 package org.onap.policy.common.gson;
 
 import com.google.gson.Gson;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
 import com.google.gson.TypeAdapter;
 import com.google.gson.TypeAdapterFactory;
 import com.google.gson.reflect.TypeToken;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -38,6 +33,7 @@
 import org.onap.policy.common.gson.internal.AnySetterDeserializer;
 import org.onap.policy.common.gson.internal.ClassWalker;
 import org.onap.policy.common.gson.internal.Deserializer;
+import org.onap.policy.common.gson.internal.JacksonTypeAdapter;
 import org.onap.policy.common.gson.internal.MethodDeserializer;
 import org.onap.policy.common.gson.internal.MethodSerializer;
 import org.onap.policy.common.gson.internal.Serializer;
@@ -65,138 +61,60 @@
         ClassWalker data = new ClassWalker();
         data.walkClassHierarchy(clazz);
 
-        if (data.getInProps(Method.class).isEmpty() && data.getOutProps(Method.class).isEmpty()) {
-            if (data.getAnyGetter() == null && data.getAnySetter() == null) {
-                // no methods to serialize
-                return null;
-            }
+        if (data.getInProps(Method.class).isEmpty() && data.getOutProps(Method.class).isEmpty()
+                        && data.getAnyGetter() == null && data.getAnySetter() == null) {
+            // no methods to serialize
+            return null;
         }
 
-        return new JacksonMethodAdapter<>(gson, data, gson.getDelegateAdapter(this, type));
+        Set<String> unliftedProps = new HashSet<>();
+        unliftedProps.addAll(data.getInNotIgnored());
+        unliftedProps.addAll(data.getOutNotIgnored());
+
+        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
+        List<Serializer> sers = makeSerializers(gson, data, unliftedProps);
+        List<Deserializer> desers = makeDeserializers(gson, data, unliftedProps);
+
+        return new JacksonTypeAdapter<>(gson, delegate, sers, desers);
     }
 
     /**
-     * Adapter for a single class.
+     * Creates a complete list of serializers.
      *
-     * @param <T> type of class on which the adapter works
+     * @param gson the associated gson object
+     * @param data data used to configure the serializers
+     * @param unliftedProps properties that should not be lowered by "any-getters"
+     * @return a list of all serializers
      */
-    private static class JacksonMethodAdapter<T> extends TypeAdapter<T> {
+    private List<Serializer> makeSerializers(Gson gson, ClassWalker data, Set<String> unliftedProps) {
+        List<Serializer> ser = new ArrayList<>();
 
-        /**
-         * Used to create an object of the given class.
-         */
-        private final TypeAdapter<T> delegate;
-
-        /**
-         * Used to serialize/deserialize a JsonElement.
-         */
-        private final TypeAdapter<JsonElement> elementAdapter;
-
-        /**
-         * Serializers for each item within the object.
-         */
-        private final Serializer[] serializers;
-
-        /**
-         * Deserializers for each item within the object.
-         */
-        private final Deserializer[] deserializers;
-
-        /**
-         * Constructs the object.
-         *
-         * @param gson the associated gson object
-         * @param data data used to configure the adapter
-         * @param delegate default constructor for the type
-         */
-        public JacksonMethodAdapter(Gson gson, ClassWalker data, TypeAdapter<T> delegate) {
-            this.delegate = delegate;
-
-            this.elementAdapter = gson.getAdapter(JsonElement.class);
-
-            Set<String> unliftedProps = new HashSet<>();
-            unliftedProps.addAll(data.getInNotIgnored());
-            unliftedProps.addAll(data.getOutNotIgnored());
-
-            // create serializers
-            this.serializers = makeSerializers(gson, data, unliftedProps).toArray(new Serializer[0]);
-
-            // create deserializers
-            this.deserializers = makeDeserializers(gson, data, unliftedProps).toArray(new Deserializer[0]);
+        if (data.getAnyGetter() != null) {
+            ser.add(new AnyGetterSerializer(gson, unliftedProps, data.getAnyGetter()));
         }
 
-        /**
-         * Creates a complete list of serializers.
-         *
-         * @param gson the associated gson object
-         * @param data data used to configure the serializers
-         * @param unliftedProps properties that should not be lowered by "any-getters"
-         * @return a list of all serializers
-         */
-        private List<Serializer> makeSerializers(Gson gson, ClassWalker data, Set<String> unliftedProps) {
-            List<Serializer> ser = new ArrayList<Serializer>();
+        data.getOutProps(Method.class).forEach(method -> ser.add(new MethodSerializer(gson, method)));
 
-            if (data.getAnyGetter() != null) {
-                ser.add(new AnyGetterSerializer(gson, unliftedProps, data.getAnyGetter()));
-            }
+        return ser;
+    }
 
-            data.getOutProps(Method.class).forEach(method -> ser.add(new MethodSerializer(gson, method)));
+    /**
+     * Creates a complete list of deserializers.
+     *
+     * @param gson the associated gson object
+     * @param data data used to configure the deserializers
+     * @param unliftedProps properties that should not be lifted by "any-setters"
+     * @return a list of all deserializers
+     */
+    private List<Deserializer> makeDeserializers(Gson gson, ClassWalker data, Set<String> unliftedProps) {
+        List<Deserializer> deser = new ArrayList<>();
 
-            return ser;
+        if (data.getAnySetter() != null) {
+            deser.add(new AnySetterDeserializer(gson, unliftedProps, data.getAnySetter()));
         }
 
-        /**
-         * Creates a complete list of deserializers.
-         *
-         * @param gson the associated gson object
-         * @param data data used to configure the deserializers
-         * @param unliftedProps properties that should not be lifted by "any-setters"
-         * @return a list of all deserializers
-         */
-        private List<Deserializer> makeDeserializers(Gson gson, ClassWalker data, Set<String> unliftedProps) {
-            List<Deserializer> deser = new ArrayList<Deserializer>();
+        data.getInProps(Method.class).forEach(method -> deser.add(new MethodDeserializer(gson, method)));
 
-            if (data.getAnySetter() != null) {
-                deser.add(new AnySetterDeserializer(gson, unliftedProps, data.getAnySetter()));
-            }
-
-            data.getInProps(Method.class).forEach(method -> deser.add(new MethodDeserializer(gson, method)));
-
-            return deser;
-        }
-
-        @Override
-        public void write(JsonWriter out, T value) throws IOException {
-            JsonElement tree = delegate.toJsonTree(value);
-
-            if (tree.isJsonObject()) {
-                JsonObject jsonObj = tree.getAsJsonObject();
-
-                // serialize each item from the value into the target tree
-                for (Serializer serializer : serializers) {
-                    serializer.addToTree(value, jsonObj);
-                }
-            }
-
-            elementAdapter.write(out, tree);
-        }
-
-        @Override
-        public T read(JsonReader in) throws IOException {
-            JsonElement tree = elementAdapter.read(in);
-
-            T object = delegate.fromJsonTree(tree);
-
-            if (tree.isJsonObject()) {
-                JsonObject jsonObj = tree.getAsJsonObject();
-
-                // deserialize each item from the tree into the target object
-                for (Deserializer dser : deserializers) {
-                    dser.getFromTree(jsonObj, object);
-                }
-            }
-
-            return object;
-        }
+        return deser;
     }
 }
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java
new file mode 100644
index 0000000..1171fd4
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/JacksonTypeAdapter.java
@@ -0,0 +1,108 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson.internal;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.List;
+
+
+/**
+ * Adapter for a single class that implements a jackson-style behavior.
+ *
+ * @param <T> type of class on which the adapter works
+ */
+public class JacksonTypeAdapter<T> extends TypeAdapter<T> {
+
+    /**
+     * Used to create an object of the given class.
+     */
+    private final TypeAdapter<T> delegate;
+
+    /**
+     * Used to serialize/deserialize a JsonElement.
+     */
+    private final TypeAdapter<JsonElement> elementAdapter;
+
+    /**
+     * Serializers for each item within the object.
+     */
+    private final Serializer[] serializers;
+
+    /**
+     * Deserializers for each item within the object.
+     */
+    private final Deserializer[] deserializers;
+
+    /**
+     * Constructs the object.
+     *
+     * @param gson the associated gson object
+     * @param delegate default constructor for the type
+     * @param serializers the serializers to use to serialize items within the object
+     * @param deserializers the deserializers to use to deserialize items into the object
+     */
+    public JacksonTypeAdapter(Gson gson, TypeAdapter<T> delegate, List<Serializer> serializers,
+                    List<Deserializer> deserializers) {
+        this.delegate = delegate;
+        this.elementAdapter = gson.getAdapter(JsonElement.class);
+        this.serializers = serializers.toArray(new Serializer[0]);
+        this.deserializers = deserializers.toArray(new Deserializer[0]);
+    }
+
+    @Override
+    public void write(JsonWriter out, T value) throws IOException {
+        JsonElement tree = delegate.toJsonTree(value);
+
+        if (tree.isJsonObject()) {
+            JsonObject jsonObj = tree.getAsJsonObject();
+
+            // serialize each item from the value into the target tree
+            for (Serializer serializer : serializers) {
+                serializer.addToTree(value, jsonObj);
+            }
+        }
+
+        elementAdapter.write(out, tree);
+    }
+
+    @Override
+    public T read(JsonReader in) throws IOException {
+        JsonElement tree = elementAdapter.read(in);
+        T object = delegate.fromJsonTree(tree);
+
+        if (tree.isJsonObject()) {
+            JsonObject jsonObj = tree.getAsJsonObject();
+
+            // deserialize each item from the tree into the target object
+            for (Deserializer dser : deserializers) {
+                dser.getFromTree(jsonObj, object);
+            }
+        }
+
+        return object;
+    }
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java
new file mode 100644
index 0000000..a75fe17
--- /dev/null
+++ b/gson/src/test/java/org/onap/policy/common/gson/internal/JacksonTypeAdapterTest.java
@@ -0,0 +1,206 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.gson.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JacksonTypeAdapterTest {
+    private static final String HELLO = "hello";
+    private static final String WORLD = "world";
+
+    /**
+     * Gson object that excludes fields, as we're going to process the fields ourselves.
+     */
+    private static Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
+
+    private JacksonTypeAdapter<Data> adapter;
+    private List<Serializer> sers;
+    private List<Deserializer> desers;
+
+    /**
+     * Initializes the previously defined fields.
+     */
+    @Before
+    public void setUp() {
+        // create list of serializers, one for "id" and one for "value"
+        sers = new ArrayList<>(2);
+        sers.add(new NamedSer(HELLO) {
+            @Override
+            protected String getValue(Data data) {
+                return data.id;
+            }
+        });
+        sers.add(new NamedSer(WORLD) {
+            @Override
+            protected String getValue(Data data) {
+                return data.value;
+            }
+        });
+
+        // create list of deserializers, one for "id" and one for "value"
+        desers = new ArrayList<>(2);
+        desers.add(new NamedDeser(HELLO) {
+            @Override
+            protected void setValue(Data data, String value) {
+                data.id = value;
+            }
+        });
+        desers.add(new NamedDeser(WORLD) {
+            @Override
+            protected void setValue(Data data, String value) {
+                data.value = value;
+            }
+        });
+
+        TypeAdapter<Data> delegate = gson.getDelegateAdapter(null, TypeToken.get(Data.class));
+
+        adapter = new JacksonTypeAdapter<>(gson, delegate, sers, desers);
+    }
+
+    @Test
+    public void testWriteJsonWriterT() throws Exception {
+        Data data = new Data("abc", "def");
+
+        StringWriter wtr = new StringWriter();
+        adapter.write(new JsonWriter(wtr), data);
+
+        assertEquals("{'hello':'abc','world':'def'}".replace('\'', '"'), wtr.toString());
+    }
+
+    /**
+     * Tests the case where the delegate does not return a JsonObject.
+     *
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWriteJsonWriterT_NotAnObject() throws Exception {
+        TypeAdapter<String> delegate = gson.getAdapter(String.class);
+        JacksonTypeAdapter<String> stringAdapter = new JacksonTypeAdapter<>(gson, delegate, sers, desers);
+
+        StringWriter wtr = new StringWriter();
+        stringAdapter.write(new JsonWriter(wtr), "write text");
+
+        assertEquals("'write text'".replace('\'', '"'), wtr.toString());
+    }
+
+    @Test
+    public void testReadJsonReader() throws Exception {
+        Data data = adapter
+                        .read(new JsonReader(new StringReader("{'hello':'four','world':'score'}".replace('\'', '"'))));
+
+        assertEquals(new Data("four", "score").toString(), data.toString());
+    }
+
+    /**
+     * Tests the case where the delegate does not use a JsonObject.
+     *
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testReadJsonReader_NotAnObject() throws Exception {
+        TypeAdapter<String> delegate = gson.getAdapter(String.class);
+        JacksonTypeAdapter<String> stringAdapter = new JacksonTypeAdapter<>(gson, delegate, sers, desers);
+
+        String data = stringAdapter.read(new JsonReader(new StringReader("'read text'".replace('\'', '"'))));
+
+        assertEquals("read text", data.toString());
+    }
+
+    private static class Data {
+        private String id;
+        private String value;
+
+        /*
+         * This is invoked by gson via reflection, thus no direct invocation. Hence it has
+         * to be labeled "unused".
+         */
+        @SuppressWarnings("unused")
+        public Data() {
+            super();
+        }
+
+        public Data(String id, String value) {
+            this.id = id;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return "Data [id=" + id + ", value=" + value + "]";
+        }
+    }
+
+    private abstract static class NamedSer implements Serializer {
+        private final String name;
+
+        /**
+         * Constructs the object.
+         *
+         * @param name the name of the field, when stored in a JsonObject
+         */
+        public NamedSer(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public void addToTree(Object source, JsonObject target) {
+            Data data = (Data) source;
+            target.addProperty(name, getValue(data));
+        }
+
+        protected abstract String getValue(Data data);
+    }
+
+    private abstract static class NamedDeser implements Deserializer {
+        private final String name;
+
+        /**
+         * Constructs the object.
+         *
+         * @param name the name of the field, when stored in a JsonObject
+         */
+        public NamedDeser(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public void getFromTree(JsonObject source, Object target) {
+            Data data = (Data) target;
+            setValue(data, source.get(name).getAsString());
+        }
+
+        protected abstract void setValue(Data data, String value);
+    }
+}