Add superclasses for gson-jackson migration

Added common classes needed by other gson-jackson code.
Modified some logic to make it more maintainable or perform better.
Updated comments and spacing.
Fix another comment.
Moved gson classes from utils to a separate gson project.
Added GsonXxx annotations to mirror jackson annotations.
Removed unneeded dependencies from gson pom.
Removed old GsonMessage class from policy-endpoints.
Removed trailing spaces.
Updated licenses.
Removed more trailing spaces.
Removed unneeded checkstyle suppression file from utils.

Change-Id: I1a285500faeb0a0b6a1467d09b92ecd3cded713e
Issue-ID: POLICY-1428
Signed-off-by: Jim Hahn <jrh3@att.com>
diff --git a/gson/checkstyle-suppressions.xml b/gson/checkstyle-suppressions.xml
new file mode 100644
index 0000000..0705e0e
--- /dev/null
+++ b/gson/checkstyle-suppressions.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!--
+  ============LICENSE_START=======================================================
+   Copyright (C) 2019 AT&T Technologies. 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.
+
+  SPDX-License-Identifier: Apache-2.0
+  ============LICENSE_END=========================================================
+-->
+
+<!--
+    NOTE:   The sole purpose of this supression file is to allow "$" in field and method
+            names so that the gson class tests can verify that those fields are ignored
+            when doing serialization and de-serialization.
+ -->
+
+<!DOCTYPE suppressions PUBLIC
+     "-//Puppy Crawl//DTD Suppressions 1.0//EN"
+     "http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
+
+<suppressions>
+  <suppress checks="MemberName"
+    files="AdapterTest.java|ClassWalkerTest.java"
+    lines="1-9999"/>
+  <suppress checks="MethodName"
+    files="AdapterTest.java"
+    lines="1-9999"/>
+</suppressions>
diff --git a/gson/pom.xml b/gson/pom.xml
new file mode 100644
index 0000000..3b9983f
--- /dev/null
+++ b/gson/pom.xml
@@ -0,0 +1,143 @@
+<!--
+  ============LICENSE_START=======================================================
+  ONAP Policy Engine - Common Modules
+  ================================================================================
+  Copyright (C) 2018-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=========================================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.policy.common</groupId>
+        <artifactId>common-modules</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>gson</artifactId>
+    <description>Common Utilities</description>
+    <packaging>jar</packaging>
+
+    <properties>
+        <!-- TODO move to top-level or parent -->
+        <jersey.version>2.25.1</jersey.version>
+        <jackson.version>2.9.5</jackson.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${jersey.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.11.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <!--This plugin's configuration is used to store Eclipse m2e settings
+                    only. It has no influence on the Maven build itself. -->
+                <plugin>
+                    <groupId>org.eclipse.m2e</groupId>
+                    <artifactId>lifecycle-mapping</artifactId>
+                    <version>1.0.0</version>
+                    <configuration>
+                        <lifecycleMappingMetadata>
+                            <pluginExecutions>
+                                <pluginExecution>
+                                    <pluginExecutionFilter>
+                                        <groupId>org.jacoco</groupId>
+                                        <artifactId>
+                                            jacoco-maven-plugin
+                                        </artifactId>
+                                        <versionRange>
+                                            [0.7.1.201405082137,)
+                                        </versionRange>
+                                        <goals>
+                                            <goal>prepare-agent</goal>
+                                        </goals>
+                                    </pluginExecutionFilter>
+                                    <action>
+                                        <ignore />
+                                    </action>
+                                </pluginExecution>
+                            </pluginExecutions>
+                        </lifecycleMappingMetadata>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>onap-java-style</id>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                        <configuration>
+                    <!-- Use Google Java Style Guide:
+                    https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
+                    with minor changes -->
+                            <configLocation>onap-checkstyle/onap-java-style.xml</configLocation>
+                    <!-- <sourceDirectory> is needed so that checkstyle ignores the generated sources directory -->
+                            <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+                            <includeResources>true</includeResources>
+                            <includeTestSourceDirectory>true</includeTestSourceDirectory>
+                            <includeTestResources>true</includeTestResources>
+                            <excludes>
+                            </excludes>
+                            <suppressionsLocation>${project.basedir}/checkstyle-suppressions.xml</suppressionsLocation>
+                            <consoleOutput>true</consoleOutput>
+                            <failsOnViolation>true</failsOnViolation>
+                            <violationSeverity>warning</violationSeverity>
+                        </configuration>
+                    </execution>
+                </executions>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.onap.oparent</groupId>
+                        <artifactId>checkstyle</artifactId>
+                        <version>${oparent.version}</version>
+                        <scope>compile</scope>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/GsonMessageBodyHandler.java b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
similarity index 93%
rename from policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/GsonMessageBodyHandler.java
rename to gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
index a29afef..2112c97 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/http/server/internal/GsonMessageBodyHandler.java
+++ b/gson/src/main/java/org/onap/policy/common/gson/GsonMessageBodyHandler.java
@@ -7,9 +7,9 @@
  * 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.
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.policy.common.endpoints.http.server.internal;
+package org.onap.policy.common.gson;
 
 import com.google.gson.Gson;
 import java.io.IOException;
@@ -40,10 +40,6 @@
 
 /**
  * Provider that serializes and de-serializes JSON via gson.
- * 
- * <p>Note: <i>jersey</i> will ignore this class if the maven artifact,
- * <i>jersey-media-json-jackson</i>, is included, regardless of whether it's included
- * directly or indirectly.
  */
 @Provider
 @Consumes(MediaType.WILDCARD)
@@ -64,7 +60,7 @@
 
     /**
      * Constructs the object.
-     * 
+     *
      * @param gson the Gson object to be used to serialize and de-serialize
      */
     public GsonMessageBodyHandler(Gson gson) {
@@ -99,7 +95,7 @@
 
     /**
      * Determines if this provider can handle the given media type.
-     * 
+     *
      * @param mediaType the media type of interest
      * @return {@code true} if this provider handles the given media type, {@code false}
      *         otherwise
diff --git a/utils/src/main/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategy.java b/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java
similarity index 98%
rename from utils/src/main/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategy.java
rename to gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java
index 96213a6..cb959c4 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategy.java
+++ b/gson/src/main/java/org/onap/policy/common/gson/JacksonExclusionStrategy.java
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.policy.common.utils.gson;
+package org.onap.policy.common.gson;
 
 import com.google.gson.ExclusionStrategy;
 import com.google.gson.FieldAttributes;
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java
new file mode 100644
index 0000000..859f538
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnyGetter.java
@@ -0,0 +1,38 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonAnyGetter annotation, but used by gson. This requires the gson
+ * object to be configured with the jackson default behaviors (i.e., the associated
+ * JacksonXxx strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface GsonJsonAnyGetter {
+
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java
new file mode 100644
index 0000000..87e0f33
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonAnySetter.java
@@ -0,0 +1,38 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonAnySetter annotation, but used by gson. This requires the gson
+ * object to be configured with the jackson default behaviors (i.e., the associated
+ * JacksonXxx strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface GsonJsonAnySetter {
+
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java
new file mode 100644
index 0000000..cf2d439
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonIgnore.java
@@ -0,0 +1,39 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonIgnore annotation, but used by gson. This requires the gson object
+ * to be configured with the jackson default behaviors (i.e., the associated JacksonXxx
+ * strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target({FIELD, METHOD})
+public @interface GsonJsonIgnore {
+
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java
new file mode 100644
index 0000000..c31c19b
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/annotation/GsonJsonProperty.java
@@ -0,0 +1,44 @@
+/*
+ * ============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.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Mimics Jackson JsonProperty annotation, but used by gson. This requires the gson object
+ * to be configured with the jackson default behaviors (i.e., the associated JacksonXxx
+ * strategy and adapters must be registered with the gson object).
+ */
+@Retention(RUNTIME)
+@Target({FIELD, METHOD})
+public @interface GsonJsonProperty {
+
+    /**
+     * Property name of this item when placed into a JsonObject.
+     * @return the item's serialized name
+     */
+    String value() default "";
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java
new file mode 100644
index 0000000..b4ef53f
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Adapter.java
@@ -0,0 +1,339 @@
+/*
+ * ============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.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import org.onap.policy.common.gson.annotation.GsonJsonProperty;
+
+/**
+ * Super class of adapters used to serialize and de-serialize an item.
+ */
+public class Adapter {
+
+    /**
+     * Pattern to match valid identifiers.
+     */
+    private static final Pattern VALID_NAME_PAT = Pattern.compile("[a-zA-Z_]\\w*");
+
+    /**
+     * Name of the property within the json structure containing the item.
+     */
+    private final String propName;
+
+    /**
+     * Gson object that will provide the type converter.
+     */
+    private final Gson gson;
+
+    /**
+     * Converter used when reading.
+     */
+    private final ConvInfo reader;
+
+    /**
+     * Converter used when writing, allocated lazily, once an actual type is determined.
+     */
+    private volatile ConvInfo writer = null;
+
+    /**
+     * Name of the item being lifted - used when throwing exceptions.
+     */
+    private final String fullName;
+
+    /**
+     * Constructs the object.
+     *
+     * @param gson Gson object providing type adapters
+     * @param field field used to access the item from within an object
+     */
+    public Adapter(Gson gson, Field field) {
+        this.propName = detmPropName(field);
+        this.reader = new ConvInfo(TypeToken.get(field.getGenericType()));
+        this.gson = gson;
+        this.fullName = getQualifiedName(field);
+
+        field.setAccessible(true);
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param gson Gson object providing type adapters
+     * @param accessor method used to access the item from within an object
+     * @param forGetter {@code true} if the name is for a "getter" method, {@code false}
+     *        if for a "setter"
+     * @param valueType the class of value on which this operates
+     */
+    public Adapter(Gson gson, Method accessor, boolean forGetter, Type valueType) {
+        this.propName = (forGetter ? detmGetterPropName(accessor) : detmSetterPropName(accessor));
+        this.reader = new ConvInfo(TypeToken.get(valueType));
+        this.gson = gson;
+        this.fullName = getQualifiedName(accessor);
+
+        accessor.setAccessible(true);
+    }
+
+    /**
+     * Converts an object to a json tree.
+     *
+     * @param object the object to be converted
+     * @return a json tree representing the object
+     */
+    @SuppressWarnings("unchecked")
+    public JsonElement toJsonTree(Object object) {
+        // always use a converter for the specific subclass
+        Class<? extends Object> clazz = object.getClass();
+
+        if (writer == null) {
+            // race condition here, but it's ok to overwrite a previous value
+            writer = new ConvInfo(TypeToken.get(clazz));
+        }
+
+        ConvInfo wtr = writer;
+        TypeAdapter<Object> conv =
+                        (TypeAdapter<Object>) (wtr.clazz == clazz ? wtr.getConverter() : gson.getAdapter(clazz));
+
+        return conv.toJsonTree(object);
+    }
+
+    /**
+     * Converts a json tree to an object.
+     *
+     * @param tree the tree to be converted
+     * @return the object represented by the tree
+     */
+    public Object fromJsonTree(JsonElement tree) {
+        return reader.getConverter().fromJsonTree(tree);
+    }
+
+    public final String getPropName() {
+        return propName;
+    }
+
+    public final String getFullName() {
+        return fullName;
+    }
+
+    /**
+     * Makes an error message, appending the item's full name to the message prefix.
+     *
+     * @param prefix the message prefix
+     * @return the error message
+     */
+    public String makeError(String prefix) {
+        return (prefix + fullName);
+    }
+
+    /**
+     * Determines if the field is managed by the walker.
+     *
+     * @param field the field to examine
+     * @return {@code true} if the field is managed by the walker, {@code false} otherwise
+     */
+    public static boolean isManaged(Field field) {
+        return VALID_NAME_PAT.matcher(field.getName()).matches();
+    }
+
+    /**
+     * Determines if the method is managed by the walker.
+     *
+     * @param method the method to examine
+     * @return {@code true} if the method is managed by the walker, {@code false}
+     *         otherwise
+     */
+    public static boolean isManaged(Method method) {
+        return VALID_NAME_PAT.matcher(method.getName()).matches();
+    }
+
+    /**
+     * Determines the property name of an item within the json structure.
+     *
+     * @param field the item within the object
+     * @return the json property name for the item or {@code null} if the name is invalid
+     */
+    public static String detmPropName(Field field) {
+        // use the serialized name, if specified
+        GsonJsonProperty prop = field.getAnnotation(GsonJsonProperty.class);
+        if (prop != null && !prop.value().isEmpty()) {
+            return prop.value();
+        }
+
+        // no name provided - use it as is
+        return (isManaged(field) ? field.getName() : null);
+    }
+
+    /**
+     * Determines the property name of an item, within the json structure, associated with
+     * a "get" method.
+     *
+     * @param method method to be invoked to get the item within the object
+     * @return the json property name for the item, or {@code null} if the method name is
+     *         not valid
+     */
+    public static String detmGetterPropName(Method method) {
+
+        return detmPropNameCommon(method, () -> {
+
+            if (!isManaged(method)) {
+                return null;
+            }
+
+            String name = method.getName();
+
+            if (name.startsWith("get")) {
+                return name.substring(3);
+
+            } else if (name.startsWith("is")) {
+                Class<?> treturn = method.getReturnType();
+
+                if (treturn == boolean.class || treturn == Boolean.class) {
+                    return name.substring(2);
+                }
+            }
+
+            // not a valid name for a "getter" method
+            return null;
+        });
+    }
+
+    /**
+     * Determines the property name of an item, within the json structure, associated with
+     * a "set" method.
+     *
+     * @param method method to be invoked to set the item within the object
+     * @return the json property name for the item, or {@code null} if the method name is
+     *         not valid
+     */
+    public static String detmSetterPropName(Method method) {
+
+        return detmPropNameCommon(method, () -> {
+
+            if (!isManaged(method)) {
+                return null;
+            }
+
+            String name = method.getName();
+
+            if (name.startsWith("set")) {
+                return name.substring(3);
+            }
+
+            // not a valid name for a "setter" method
+            return null;
+        });
+    }
+
+    /**
+     * Determines the property name of an item within the json structure.
+     *
+     * @param method method to be invoked to get/set the item within the object
+     * @param extractor function to extract the name directly from the method name
+     * @return the json property name for the item, or {@code null} if the method name is
+     *         not valid
+     */
+    private static String detmPropNameCommon(Method method, Supplier<String> extractor) {
+
+        // use the property name, if specified
+        GsonJsonProperty propName = method.getAnnotation(GsonJsonProperty.class);
+        if (propName != null && !propName.value().isEmpty()) {
+            return propName.value();
+        }
+
+        // no name provided - must compute it from the method name
+        String name = extractor.get();
+
+        if (name == null || name.isEmpty()) {
+            // nothing left after stripping the prefix - invalid name
+            return null;
+        }
+
+        // translate the first letter to lower-case
+        return name.substring(0, 1).toLowerCase() + name.substring(1);
+    }
+
+    /**
+     * Gets the fully qualified name of a field.
+     *
+     * @param field field whose name is desired
+     * @return the field fully qualified name
+     */
+    public static String getQualifiedName(Field field) {
+        return (field.getDeclaringClass().getName() + "." + field.getName());
+    }
+
+    /**
+     * Gets the fully qualified name of a method.
+     *
+     * @param method method whose name is desired
+     * @return the method's fully qualified name
+     */
+    public static String getQualifiedName(Method method) {
+        return (method.getDeclaringClass().getName() + "." + method.getName());
+    }
+
+    /**
+     * Converter info.
+     */
+    private class ConvInfo {
+
+        /**
+         * Type on which the converter works.
+         */
+        private TypeToken<?> type;
+
+        /**
+         * Class of object on which the converter works.
+         */
+        private Class<?> clazz;
+
+        /**
+         * Converter to use, initialized lazily.
+         */
+        private volatile TypeAdapter<?> conv = null;
+
+        /**
+         * Constructs the object.
+         *
+         * @param type type of object to be converted
+         */
+        public ConvInfo(TypeToken<?> type) {
+            this.type = type;
+            this.clazz = type.getRawType();
+        }
+
+        public final TypeAdapter<?> getConverter() {
+            if (conv == null) {
+                // race condition here, but it's ok to overwrite a previous value
+                this.conv = gson.getAdapter(type);
+            }
+
+            return conv;
+        }
+    }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java b/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java
new file mode 100644
index 0000000..e985d98
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/ClassWalker.java
@@ -0,0 +1,389 @@
+/*
+ * ============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.JsonParseException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.onap.policy.common.gson.annotation.GsonJsonAnyGetter;
+import org.onap.policy.common.gson.annotation.GsonJsonAnySetter;
+import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
+import org.onap.policy.common.gson.annotation.GsonJsonProperty;
+
+/**
+ * Data populated while walking the hierarchy of a class.
+ */
+public class ClassWalker {
+
+    public static final String ANY_GETTER_MISMATCH_ERR =
+                    GsonJsonAnyGetter.class.getSimpleName() + " parameter mismatch for: ";
+
+    public static final String ANY_SETTER_MISMATCH_ERR =
+                    GsonJsonAnySetter.class.getSimpleName() + " parameter mismatch for: ";
+
+    public static final String ANY_SETTER_TYPE_ERR =
+                    GsonJsonAnySetter.class.getSimpleName() + " first parameter must be a string: ";
+
+    /**
+     * Maps an input property name to an item within the class, where item is one of:
+     * {@link Field}, {@link Method}, or {@code null}. Entries are overwritten as new
+     * items are added.
+     */
+    private final Map<String, Object> inProps = new HashMap<>();
+
+    /**
+     * Maps an output property name to an item within the class, where item is one of:
+     * {@link Field}, {@link Method}, or {@code null}. Entries are overwritten as new
+     * items are added.
+     */
+    private final Map<String, Object> outProps = new HashMap<>();
+
+    /**
+     * Maps a method name to a "get" method. Used when overriding properties associated
+     * with a method.
+     */
+    private final Map<String, Method> getters = new HashMap<>();
+
+    /**
+     * Maps a method name to a "set" method. Used when overriding properties associated
+     * with a method.
+     */
+    private final Map<String, Method> setters = new HashMap<>();
+
+    /**
+     * Method having {@link GsonJsonAnyGetter} annotation. Overwritten as new "any-getters"
+     * are identified.
+     */
+    private Method anyGetter = null;
+
+    /**
+     * Method having {@link GsonJsonAnySetter} annotation. Overwritten as new "any-setters"
+     * are identified.
+     */
+    private Method anySetter = null;
+
+
+    public Method getAnyGetter() {
+        return anyGetter;
+    }
+
+    public Method getAnySetter() {
+        return anySetter;
+    }
+
+    /**
+     * Gets the names of input properties that are not being ignored.
+     *
+     * @return the non-ignored input property names
+     */
+    public List<String> getInNotIgnored() {
+        return getNonNull(inProps);
+    }
+
+    /**
+     * Gets the names of output properties that are not being ignored.
+     *
+     * @return the non-ignored output property names
+     */
+    public List<String> getOutNotIgnored() {
+        return getNonNull(outProps);
+    }
+
+    /**
+     * Gets the property names, associated with a non-null value, from a set of
+     * properties.
+     *
+     * @param props set of properties from which to extract the names
+     * @return the property names having a non-null value
+     */
+    private List<String> getNonNull(Map<String, Object> props) {
+        List<String> lst = new ArrayList<String>(props.size());
+
+        for (Entry<String, Object> ent : props.entrySet()) {
+            if (ent.getValue() != null) {
+                lst.add(ent.getKey());
+            }
+        }
+
+        return lst;
+    }
+
+    /**
+     * Gets the input properties whose values are of the given class.
+     *
+     * @param clazz class of properties to get
+     * @return the input properties of the given class
+     */
+    public <T> List<T> getInProps(Class<T> clazz) {
+        return getProps(clazz, inProps.values());
+    }
+
+    /**
+     * Gets the output properties whose values are of the given class.
+     *
+     * @param clazz class of properties to get
+     * @return the output properties of the given class
+     */
+    public <T> List<T> getOutProps(Class<T> clazz) {
+        return getProps(clazz, outProps.values());
+    }
+
+    /**
+     * Gets the properties whose values are of the given class.
+     *
+     * @param clazz class of properties to get
+     * @param values values from which to select
+     * @return the output properties of the given class
+     */
+    @SuppressWarnings("unchecked")
+    private <T> List<T> getProps(Class<T> clazz, Collection<Object> values) {
+        List<T> lst = new ArrayList<T>(values.size());
+
+        for (Object val : values) {
+            if (val != null && val.getClass() == clazz) {
+                lst.add((T) val);
+            }
+        }
+
+        return lst;
+    }
+
+    /**
+     * Recursively walks a class hierarchy, including super classes and interfaces,
+     * examining each class for various annotations.
+     *
+     * @param clazz class whose hierarchy is to be walked
+     */
+    public void walkClassHierarchy(Class<?> clazz) {
+        if (clazz == Object.class) {
+            return;
+        }
+
+        // walk interfaces first
+        for (Class<?> intfc : clazz.getInterfaces()) {
+            walkClassHierarchy(intfc);
+        }
+
+        // walk superclass next, overwriting previous items
+        Class<?> sup = clazz.getSuperclass();
+        if (sup != null) {
+            walkClassHierarchy(sup);
+        }
+
+        // finally, examine this class, overwriting previous items
+        examine(clazz);
+    }
+
+    /**
+     * Examines a class for annotations, examining fields and then methods.
+     *
+     * @param clazz class to be examined
+     */
+    protected void examine(Class<?> clazz) {
+        for (Field field : clazz.getDeclaredFields()) {
+            examine(field);
+        }
+
+        for (Method method : clazz.getDeclaredMethods()) {
+            examine(method);
+        }
+    }
+
+    /**
+     * Examines a field for annotations.
+     *
+     * @param field field to be examined
+     */
+    protected void examine(Field field) {
+        if (field.isSynthetic()) {
+            return;
+        }
+
+        int mod = field.getModifiers();
+
+        if (Modifier.isStatic(mod)) {
+            // skip static fields
+            return;
+        }
+
+        if (!Modifier.isPublic(mod) && field.getAnnotation(GsonJsonProperty.class) == null) {
+            // private/protected - skip it unless explicitly exposed
+            return;
+        }
+
+        if (Modifier.isTransient(mod) && field.getAnnotation(GsonJsonProperty.class) == null) {
+            // transient - skip it unless explicitly exposed
+            return;
+        }
+
+        String name = Adapter.detmPropName(field);
+        if (name == null) {
+            // invalid name
+            return;
+        }
+
+        // if ignoring, then insert null into the map, otherwise insert the field
+        Field annotField = (field.getAnnotation(GsonJsonIgnore.class) != null ? null : field);
+
+        // a field can be both an input and an output
+
+        inProps.put(name, annotField);
+        outProps.put(name, annotField);
+    }
+
+    /**
+     * Examines a method for annotations.
+     *
+     * @param method method to be examined
+     */
+    protected void examine(Method method) {
+        if (method.isSynthetic()) {
+            return;
+        }
+
+        int mod = method.getModifiers();
+
+        if (Modifier.isStatic(mod)) {
+            // static methods are not exposed
+            return;
+        }
+
+        GsonJsonProperty prop = method.getAnnotation(GsonJsonProperty.class);
+        GsonJsonAnyGetter get = method.getAnnotation(GsonJsonAnyGetter.class);
+        GsonJsonAnySetter set = method.getAnnotation(GsonJsonAnySetter.class);
+
+        if (!Modifier.isPublic(mod) && prop == null && get == null && set == null) {
+            // private/protected methods are not exposed, unless annotated
+            return;
+        }
+
+
+        if (method.getReturnType() == void.class) {
+            // "void" return type - must be a "setter" method
+            if (set == null) {
+                examineSetter(method);
+
+            } else {
+                examineAnySetter(method);
+            }
+
+        } else {
+            // must be a "getter" method
+            if (get == null) {
+                examineGetter(method);
+
+            } else {
+                examineAnyGetter(method);
+            }
+        }
+    }
+
+    /**
+     * Examines a "setter" method.
+     *
+     * @param method method to be examined
+     */
+    private void examineSetter(Method method) {
+        String name = Adapter.detmSetterPropName(method);
+        if (name != null && method.getParameterCount() == 1) {
+            // remove old name mapping, if any
+            Method old = setters.get(method.getName());
+            if (old != null) {
+                inProps.remove(Adapter.detmSetterPropName(old));
+            }
+
+            setters.put(method.getName(), method);
+
+            // if ignoring, then insert null into the map, otherwise insert the method
+            inProps.put(name, (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method));
+        }
+    }
+
+    /**
+     * Examines a "getter" method.
+     *
+     * @param method method to be examined
+     */
+    private void examineGetter(Method method) {
+        String name = Adapter.detmGetterPropName(method);
+        if (name != null && method.getParameterCount() == 0) {
+            // remove old name mapping, if any
+            Method old = getters.get(method.getName());
+            if (old != null) {
+                outProps.remove(Adapter.detmGetterPropName(old));
+            }
+
+            getters.put(method.getName(), method);
+
+            // if ignoring, then insert null into the map, otherwise insert the method
+            outProps.put(name, (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method));
+        }
+    }
+
+    /**
+     * Examines a method having a {@link GsonJsonAnySetter} annotation.
+     *
+     * @param method method to be examined
+     */
+    private void examineAnySetter(Method method) {
+        if (method.getParameterCount() != 2) {
+            throw new JsonParseException(ANY_SETTER_MISMATCH_ERR + getFqdn(method));
+        }
+
+        if (method.getParameterTypes()[0] != String.class) {
+            throw new JsonParseException(ANY_SETTER_TYPE_ERR + getFqdn(method));
+        }
+
+        // if ignoring, then use null, otherwise use the method
+        anySetter = (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method);
+    }
+
+    /**
+     * Examines a method having a {@link GsonJsonAnyGetter} annotation.
+     *
+     * @param method method to be examined
+     */
+    private void examineAnyGetter(Method method) {
+        if (method.getParameterCount() != 0) {
+            throw new JsonParseException(ANY_GETTER_MISMATCH_ERR + getFqdn(method));
+        }
+
+        // if ignoring, then use null, otherwise use the method
+        anyGetter = (method.getAnnotation(GsonJsonIgnore.class) != null ? null : method);
+    }
+
+    /**
+     * Gets the fully qualified name of a method.
+     *
+     * @param method method whose name is desired
+     * @return the fully qualified method name
+     */
+    private String getFqdn(Method method) {
+        return (method.getDeclaringClass().getName() + "." + method.getName());
+    }
+}
diff --git a/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java b/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java
new file mode 100644
index 0000000..f297586
--- /dev/null
+++ b/gson/src/main/java/org/onap/policy/common/gson/internal/Deserializer.java
@@ -0,0 +1,39 @@
+/*
+ * ============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.JsonObject;
+
+/**
+ * Super class of all de-serializers.
+ */
+public interface Deserializer {
+
+    String INVOKE_ERR = "cannot invoke method to deserialize: ";
+
+    /**
+     * Gets an value from a tree, converts it, and puts it into a target object.
+     *
+     * @param source tree from which to get the value
+     * @param target where to place the converted value
+     */
+    void getFromTree(JsonObject source, Object target);
+}
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/GsonMessageBodyHandlerTest.java b/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java
similarity index 96%
rename from policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/GsonMessageBodyHandlerTest.java
rename to gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java
index 9c6ec80..85ecfea 100644
--- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/GsonMessageBodyHandlerTest.java
+++ b/gson/src/test/java/org/onap/policy/common/gson/GsonMessageBodyHandlerTest.java
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.policy.common.endpoints.http.server.test;
+package org.onap.policy.common.gson;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -29,7 +29,7 @@
 import javax.ws.rs.core.MediaType;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.endpoints.http.server.internal.GsonMessageBodyHandler;
+import org.onap.policy.common.gson.GsonMessageBodyHandler;
 
 public class GsonMessageBodyHandlerTest {
     private static final String GEN_TYPE = "some-type";
diff --git a/utils/src/test/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategyTest.java b/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java
similarity index 98%
rename from utils/src/test/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategyTest.java
rename to gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java
index e892790..4b5473c 100644
--- a/utils/src/test/java/org/onap/policy/common/utils/gson/JacksonExclusionStrategyTest.java
+++ b/gson/src/test/java/org/onap/policy/common/gson/JacksonExclusionStrategyTest.java
@@ -18,7 +18,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.policy.common.utils.gson;
+package org.onap.policy.common.gson;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -33,6 +33,7 @@
 import java.util.TreeMap;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onap.policy.common.gson.JacksonExclusionStrategy;
 
 public class JacksonExclusionStrategyTest {
 
diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java
new file mode 100644
index 0000000..fcb0d9a
--- /dev/null
+++ b/gson/src/test/java/org/onap/policy/common/gson/internal/AdapterTest.java
@@ -0,0 +1,387 @@
+/*
+ * ============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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+import org.junit.Test;
+import org.onap.policy.common.gson.JacksonExclusionStrategy;
+import org.onap.policy.common.gson.annotation.GsonJsonProperty;
+import org.onap.policy.common.gson.internal.Adapter;
+import org.onap.policy.common.gson.internal.DataAdapterFactory.Data;
+import org.onap.policy.common.gson.internal.DataAdapterFactory.DerivedData;
+
+public class AdapterTest {
+    private static final String GET_VALUE_NAME = "getValue";
+    private static final String VALUE_NAME = "value";
+    private static final String MY_NAME = AdapterTest.class.getName();
+
+    private static DataAdapterFactory dataAdapter = new DataAdapterFactory();
+
+    private static Gson gson = new GsonBuilder().registerTypeAdapterFactory(dataAdapter)
+                    .setExclusionStrategies(new JacksonExclusionStrategy()).create();
+
+    /*
+     * The remaining fields are just used within the tests.
+     */
+
+    private String value;
+
+    // empty alias - should use field name
+    @GsonJsonProperty("")
+    protected String emptyAlias;
+
+    @GsonJsonProperty("name-with-alias")
+    protected String nameWithAlias;
+
+    protected String unaliased;
+
+    protected String $invalidFieldName;
+
+    private List<Data> listField;
+
+    private Data dataField;
+
+
+    @Test
+    public void testIsManagedField() {
+        assertTrue(Adapter.isManaged(field(VALUE_NAME)));
+
+        assertFalse(Adapter.isManaged(field("$invalidFieldName")));
+    }
+
+    @Test
+    public void testIsManagedMethod() {
+        assertTrue(Adapter.isManaged(mget(GET_VALUE_NAME)));
+
+        assertFalse(Adapter.isManaged(mget("get$InvalidName")));
+        assertFalse(Adapter.isManaged(mset("set$InvalidName")));
+    }
+
+    @Test
+    public void testAdapterField_Converter() {
+        Adapter adapter = new Adapter(gson, field("dataField"));
+
+        // first, write something of type Data
+        dataAdapter.reset();
+        dataField = new Data(300);
+        JsonElement tree = adapter.toJsonTree(dataField);
+        assertEquals("{'id':300}".replace('\'', '"'), tree.toString());
+
+        // now try a subclass
+        dataAdapter.reset();
+        dataField = new DerivedData(300, "three");
+        tree = adapter.toJsonTree(dataField);
+        assertEquals("{'id':300,'text':'three'}".replace('\'', '"'), tree.toString());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAdapterField_Converter_List() {
+        listField = DataAdapterFactory.makeList();
+
+        Adapter adapter = new Adapter(gson, field("listField"));
+
+        dataAdapter.reset();
+        JsonElement tree = adapter.toJsonTree(listField);
+        assertTrue(dataAdapter.isDataWritten());
+        assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString());
+
+        // encode it twice so it uses the cached converter
+        dataAdapter.reset();
+        tree = adapter.toJsonTree(listField);
+        assertTrue(dataAdapter.isDataWritten());
+        assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString());
+
+        dataAdapter.reset();
+        List<Data> lst2 = (List<Data>) adapter.fromJsonTree(tree);
+        assertTrue(dataAdapter.isDataRead());
+
+        assertEquals(listField.toString(), lst2.toString());
+
+        // decode it twice so it uses the cached converter
+        dataAdapter.reset();
+        lst2 = (List<Data>) adapter.fromJsonTree(tree);
+        assertTrue(dataAdapter.isDataRead());
+
+        assertEquals(listField.toString(), lst2.toString());
+    }
+
+    @Test
+    public void testAdapterMethod_Converter() throws Exception {
+        listField = DataAdapterFactory.makeList();
+
+        Method getter = mget("getMyList");
+
+        Adapter aget = new Adapter(gson, getter, true, getter.getReturnType());
+
+        dataAdapter.reset();
+        JsonElement tree = aget.toJsonTree(listField);
+        assertTrue(dataAdapter.isDataWritten());
+        assertEquals(DataAdapterFactory.ENCODED_LIST, tree.toString());
+
+        Method setter = AdapterTest.class.getDeclaredMethod("setMyList", List.class);
+        Adapter aset = new Adapter(gson, setter, true, setter.getGenericParameterTypes()[0]);
+
+        dataAdapter.reset();
+        @SuppressWarnings("unchecked")
+        List<Data> lst2 = (List<Data>) aset.fromJsonTree(tree);
+        assertTrue(dataAdapter.isDataRead());
+
+        assertEquals(listField.toString(), lst2.toString());
+    }
+
+    @Test
+    public void testGetPropName_testGetFullName_testMakeError() {
+        // test field
+        Adapter adapter = new Adapter(gson, field(VALUE_NAME));
+
+        assertEquals(VALUE_NAME, adapter.getPropName());
+        assertEquals(MY_NAME + ".value", adapter.getFullName());
+
+
+        // test getter
+        adapter = new Adapter(gson, mget(GET_VALUE_NAME), true, String.class);
+
+        assertEquals(VALUE_NAME, adapter.getPropName());
+        assertEquals(MY_NAME + ".getValue", adapter.getFullName());
+
+        assertEquals("hello: " + MY_NAME + ".getValue", adapter.makeError("hello: "));
+
+
+        // test setter
+        adapter = new Adapter(gson, mset("setValue"), false, String.class);
+
+        assertEquals(VALUE_NAME, adapter.getPropName());
+        assertEquals(MY_NAME + ".setValue", adapter.getFullName());
+    }
+
+    @Test
+    public void testToJsonTree() {
+        Adapter adapter = new Adapter(gson, field(VALUE_NAME));
+
+        JsonElement tree = adapter.toJsonTree("hello");
+        assertTrue(tree.isJsonPrimitive());
+        assertEquals("hello", tree.getAsString());
+    }
+
+    @Test
+    public void testFromJsonTree() {
+        Adapter adapter = new Adapter(gson, field(VALUE_NAME));
+
+        assertEquals("world", adapter.fromJsonTree(new JsonPrimitive("world")));
+    }
+
+    @Test
+    public void testDetmPropName() {
+        assertEquals("emptyAlias", Adapter.detmPropName(field("emptyAlias")));
+        assertEquals("name-with-alias", Adapter.detmPropName(field("nameWithAlias")));
+        assertEquals("unaliased", Adapter.detmPropName(field("unaliased")));
+        assertEquals(null, Adapter.detmPropName(field("$invalidFieldName")));
+    }
+
+    @Test
+    public void testDetmGetterPropName() {
+        assertEquals("emptyAlias", Adapter.detmGetterPropName(mget("getEmptyAlias")));
+        assertEquals("get-with-alias", Adapter.detmGetterPropName(mget("getWithAlias")));
+        assertEquals("plain", Adapter.detmGetterPropName(mget("getPlain")));
+        assertEquals("primBool", Adapter.detmGetterPropName(mget("isPrimBool")));
+        assertEquals("boxedBool", Adapter.detmGetterPropName(mget("isBoxedBool")));
+        assertEquals(null, Adapter.detmGetterPropName(mget("isString")));
+        assertEquals(null, Adapter.detmGetterPropName(mget("noGet")));
+        assertEquals(null, Adapter.detmGetterPropName(mget("get")));
+        assertEquals(null, Adapter.detmGetterPropName(mget("get$InvalidName")));
+    }
+
+    @Test
+    public void testDetmSetterPropName() {
+        assertEquals("emptyAlias", Adapter.detmSetterPropName(mset("setEmptyAlias")));
+        assertEquals("set-with-alias", Adapter.detmSetterPropName(mset("setWithAlias")));
+        assertEquals("plain", Adapter.detmSetterPropName(mset("setPlain")));
+        assertEquals(null, Adapter.detmSetterPropName(mset("noSet")));
+        assertEquals(null, Adapter.detmSetterPropName(mset("set")));
+        assertEquals(null, Adapter.detmSetterPropName(mset("set$InvalidName")));
+    }
+
+    @Test
+    public void testGetQualifiedNameField() throws Exception {
+        assertEquals(MY_NAME + ".value", Adapter.getQualifiedName(AdapterTest.class.getDeclaredField(VALUE_NAME)));
+    }
+
+    @Test
+    public void testGetQualifiedNameMethod() throws Exception {
+        assertEquals(MY_NAME + ".getValue", Adapter.getQualifiedName(mget(GET_VALUE_NAME)));
+    }
+
+    /**
+     * Gets a field from this class, by name.
+     *
+     * @param name name of the field to get
+     * @return the field
+     */
+    private Field field(String name) {
+        try {
+            return AdapterTest.class.getDeclaredField(name);
+
+        } catch (SecurityException | NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets a "getter" method from this class, by name.
+     *
+     * @param name name of the method to get
+     * @return the method
+     */
+    private Method mget(String name) {
+        try {
+            return AdapterTest.class.getDeclaredMethod(name);
+
+        } catch (NoSuchMethodException | SecurityException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Gets a "setter" method from this class, by name.
+     *
+     * @param name name of the method to get
+     * @return the method
+     */
+    private Method mset(String name) {
+        try {
+            return AdapterTest.class.getDeclaredMethod(name, String.class);
+
+        } catch (NoSuchMethodException | SecurityException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /*
+     * The remaining methods are just used within the tests.
+     */
+
+    protected String getValue() {
+        return value;
+    }
+
+    // empty alias - should use method name
+    @GsonJsonProperty("")
+    protected String getEmptyAlias() {
+        return "";
+    }
+
+    @GsonJsonProperty("get-with-alias")
+    protected String getWithAlias() {
+        return "";
+    }
+
+    // no alias, begins with "get"
+    protected String getPlain() {
+        return "";
+    }
+
+    // begins with "is", returns primitive boolean
+    protected boolean isPrimBool() {
+        return true;
+    }
+
+    // begins with "is", returns boxed Boolean
+    protected Boolean isBoxedBool() {
+        return true;
+    }
+
+    // begins with "is", but doesn't return a boolean
+    protected String isString() {
+        return "";
+    }
+
+    // doesn't begin with "get"
+    protected String noGet() {
+        return "";
+    }
+
+    // nothing after "get"
+    protected String get() {
+        return "";
+    }
+
+    // name has a bogus character
+    protected String get$InvalidName() {
+        return "";
+    }
+
+
+    protected void setValue(String text) {
+        // do nothing
+    }
+
+    // empty alias - should use method name
+    @GsonJsonProperty("")
+    protected void setEmptyAlias(String text) {
+        // do nothing
+    }
+
+    @GsonJsonProperty("set-with-alias")
+    protected void setWithAlias(String text) {
+        // do nothing
+    }
+
+    // no alias, begins with "set"
+    protected void setPlain(String text) {
+        // do nothing
+    }
+
+    // doesn't begin with "set"
+    protected void noSet(String text) {
+        // do nothing
+    }
+
+    // nothing after "get"
+    protected void set(String text) {
+        // do nothing
+    }
+
+    // name has a bogus character
+    protected void set$InvalidName(String text) {
+        // do nothing
+    }
+
+    // returns a list
+    protected List<Data> getMyList() {
+        return listField;
+    }
+
+    // accepts a list
+    protected void setMyList(List<Data> newList) {
+        listField = newList;
+    }
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java b/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java
new file mode 100644
index 0000000..1a15be0
--- /dev/null
+++ b/gson/src/test/java/org/onap/policy/common/gson/internal/ClassWalkerTest.java
@@ -0,0 +1,507 @@
+/*
+ * ============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.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.google.gson.JsonParseException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.gson.annotation.GsonJsonAnyGetter;
+import org.onap.policy.common.gson.annotation.GsonJsonAnySetter;
+import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
+import org.onap.policy.common.gson.annotation.GsonJsonProperty;
+import org.onap.policy.common.gson.internal.Adapter;
+import org.onap.policy.common.gson.internal.ClassWalker;
+
+public class ClassWalkerTest {
+
+    private MyWalker walker;
+
+    /**
+     * Set up.
+     */
+    @Before
+    public void setUp() {
+        walker = new MyWalker();
+    }
+
+    @Test
+    public void testExamineClassOfQ_testExamineField_testExamineInField_testExamineOutField() {
+        walker.walkClassHierarchy(DerivedFromBottom.class);
+
+        assertEquals("[Intfc1, Intfc2, Intfc1, Intfc3, Bottom, DerivedFromBottom]", walker.classes.toString());
+
+        List<String> inFields = walker.getInProps(Field.class).stream().map(field -> field.getName())
+                        .collect(Collectors.toList());
+        Collections.sort(inFields);
+        assertEquals("[exposedField, overriddenValue, transField]", inFields.toString());
+
+        List<String> outFields = walker.getInProps(Field.class).stream().map(field -> field.getName())
+                        .collect(Collectors.toList());
+        Collections.sort(outFields);
+        assertEquals("[exposedField, overriddenValue, transField]", outFields.toString());
+
+        // should work with interfaces without throwing an NPE
+        walker.walkClassHierarchy(Intfc1.class);
+    }
+
+    @Test
+    public void testHasAnyGetter() {
+        walker.walkClassHierarchy(Object.class);
+        assertNull(walker.getAnyGetter());
+        assertNull(walker.getAnySetter());
+
+        walker.walkClassHierarchy(AnyGetterIgnored.class);
+        assertNull(walker.getAnyGetter());
+        assertNull(walker.getAnySetter());
+
+        walker.walkClassHierarchy(AnyGetterOnly.class);
+        assertNotNull(walker.getAnyGetter());
+        assertNull(walker.getAnySetter());
+    }
+
+    @Test
+    public void testHasAnySetter() {
+        walker.walkClassHierarchy(Object.class);
+        assertNull(walker.getAnySetter());
+        assertNull(walker.getAnyGetter());
+
+        walker.walkClassHierarchy(AnySetterIgnored.class);
+        assertNull(walker.getAnySetter());
+        assertNull(walker.getAnyGetter());
+
+        walker.walkClassHierarchy(AnySetterOnly.class);
+        assertNotNull(walker.getAnySetter());
+        assertNull(walker.getAnyGetter());
+    }
+
+    @Test
+    public void testExamineMethod() {
+        walker.walkClassHierarchy(DerivedFromData.class);
+
+        assertEquals("[Data, DerivedFromData]", walker.classes.toString());
+
+        // ensure all methods were examined
+        Collections.sort(walker.methods);
+        List<String> lst = Arrays.asList("getId", "getValue", "getOnlyOut", "getStatic", "getText", "getTheMap",
+                        "getUnserialized", "getValue", "getWithParams", "setExtraParams", "setId", "setMap",
+                        "setMapValue", "setMissingParams", "setNonPublic", "setOnlyIn", "setText", "setUnserialized",
+                        "setValue", "setValue", "wrongGetPrefix", "wrongSetPrefix");
+        Collections.sort(lst);
+        assertEquals(lst.toString(), walker.methods.toString());
+
+        assertNotNull(walker.getAnyGetter());
+        assertEquals("getTheMap", walker.getAnyGetter().getName());
+
+        List<String> getters = walker.getOutProps(Method.class).stream().map(method -> method.getName())
+                        .collect(Collectors.toList());
+        Collections.sort(getters);
+        assertEquals("[getId, getOnlyOut, getValue]", getters.toString());
+
+        assertNotNull(walker.getAnySetter());
+        assertEquals("setMapValue", walker.getAnySetter().getName());
+
+        List<String> setters = walker.getInProps(Method.class).stream().map(method -> method.getName())
+                        .collect(Collectors.toList());
+        Collections.sort(setters);
+        assertEquals("[setId, setOnlyIn, setValue]", setters.toString());
+
+        // getter with invalid parameter count
+        assertThatThrownBy(() -> walker.walkClassHierarchy(AnyGetterMismatchParams.class))
+                        .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_GETTER_MISMATCH_ERR
+                                        + AnyGetterMismatchParams.class.getName() + ".getTheMap");
+
+        // setter with too few parameters
+        assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterTooFewParams.class))
+                        .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_MISMATCH_ERR
+                                        + AnySetterTooFewParams.class.getName() + ".setOverride");
+
+        // setter with too many parameters
+        assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterTooManyParams.class))
+                        .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_MISMATCH_ERR
+                                        + AnySetterTooManyParams.class.getName() + ".setOverride");
+
+        // setter with invalid parameter type
+        assertThatThrownBy(() -> walker.walkClassHierarchy(AnySetterInvalidParam.class))
+                        .isInstanceOf(JsonParseException.class).hasMessage(ClassWalker.ANY_SETTER_TYPE_ERR
+                                        + AnySetterInvalidParam.class.getName() + ".setOverride");
+    }
+
+    @Test
+    public void testExamineMethod_AnyGetter() {
+        walker.walkClassHierarchy(AnyGetterOverride.class);
+
+        assertNotNull(walker.getAnyGetter());
+        assertEquals("getOverride", walker.getAnyGetter().getName());
+    }
+
+    @Test
+    public void testExamineMethod_AnySetter() {
+        walker.walkClassHierarchy(AnySetterOverride.class);
+
+        assertNotNull(walker.getAnySetter());
+        assertEquals("setOverride", walker.getAnySetter().getName());
+    }
+
+    @Test
+    public void testGetInNotIgnored_testGetOutNotIgnored() {
+        walker.walkClassHierarchy(DerivedFromData.class);
+
+        assertEquals("[id, onlyIn, text, value]", new TreeSet<>(walker.getInNotIgnored()).toString());
+        assertEquals("[id, onlyOut, text, value]", new TreeSet<>(walker.getOutNotIgnored()).toString());
+    }
+
+    /**
+     * Walker subclass that records items that are examined.
+     */
+    private static class MyWalker extends ClassWalker {
+        private List<String> classes = new ArrayList<>();
+        private List<String> methods = new ArrayList<>();
+
+        @Override
+        protected void examine(Class<?> clazz) {
+            classes.add(clazz.getSimpleName());
+
+            super.examine(clazz);
+        }
+
+        @Override
+        protected void examine(Method method) {
+            if (Adapter.isManaged(method)) {
+                methods.add(method.getName());
+            }
+
+            super.examine(method);
+        }
+    }
+
+    protected static interface Intfc1 {
+        int id = 1000;
+    }
+
+    protected static interface Intfc2 {
+        String text = "intfc2-text";
+    }
+
+    private static interface Intfc3 {
+
+    }
+
+    protected static class Bottom implements Intfc1, Intfc3 {
+        private int id;
+        public String value;
+
+        public String invalid$fieldName;
+
+        @GsonJsonProperty("exposed")
+        private String exposedField;
+
+        @GsonJsonIgnore
+        public int ignored;
+
+        public transient int ignoredTransField;
+
+        @GsonJsonProperty("trans")
+        public transient int transField;
+
+        @GsonJsonIgnore
+        public int getId() {
+            return id;
+        }
+
+        @GsonJsonIgnore
+        public void setId(int id) {
+            this.id = id;
+        }
+    }
+
+    protected static class DerivedFromBottom extends Bottom implements Intfc1, Intfc2 {
+        private String text;
+        protected String anotherValue;
+
+        @GsonJsonProperty("value")
+        public String overriddenValue;
+
+        @GsonJsonIgnore
+        public String getText() {
+            return text;
+        }
+
+        @GsonJsonIgnore
+        public void setText(String text) {
+            this.text = text;
+        }
+    }
+
+    protected static class Data {
+        private int id;
+        private String text;
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        // not public, but property provided
+        @GsonJsonProperty("text")
+        protected String getText() {
+            return text;
+        }
+
+        // this will be ignored, because there's already a field by this name
+        public void setText(String text) {
+            this.text = text;
+        }
+
+        // should only show up in the output list
+        public int getOnlyOut() {
+            return 1100;
+        }
+
+        // will be overridden by subclass
+        @GsonJsonProperty("super-value-getter")
+        public String getValue() {
+            return null;
+        }
+
+        // will be overridden by subclass
+        @GsonJsonProperty("super-value-setter")
+        public void setValue(String value) {
+            // do nothing
+        }
+    }
+
+    protected static class DerivedFromData extends Data {
+        // not serialized
+        private String unserialized;
+
+        // overrides private field and public method from Data
+        public String text;
+
+        private Map<String, String> map;
+
+        private String value;
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+
+        @GsonJsonAnyGetter
+        public Map<String, String> getTheMap() {
+            return map;
+        }
+
+        @GsonJsonIgnore
+        public void setMap(Map<String, String> map) {
+            this.map = map;
+        }
+
+        @GsonJsonAnySetter
+        public void setMapValue(String key, String value) {
+            if (map == null) {
+                map = new TreeMap<>();
+            }
+
+            map.put(key, value);
+        }
+
+        @GsonJsonIgnore
+        public String getUnserialized() {
+            return unserialized;
+        }
+
+        @GsonJsonIgnore
+        public void setUnserialized(String unserialized) {
+            this.unserialized = unserialized;
+        }
+
+        // should only show up in the input list
+        public void setOnlyIn(int value) {
+            // do nothing
+        }
+
+        // has a param - shouldn't be serialized
+        public int getWithParams(String text) {
+            return 1000;
+        }
+
+        // too few params - shouldn't be serialized
+        public void setMissingParams() {
+            // do nothing
+        }
+
+        // too many params - shouldn't be serialized
+        public void setExtraParams(String text, String moreText) {
+            // do nothing
+        }
+
+        // not public - shouldn't be serialized
+        protected void setNonPublic(String text) {
+            // do nothing
+        }
+
+        // doesn't start with "get"
+        public String wrongGetPrefix() {
+            return null;
+        }
+
+        // doesn't start with "set"
+        public void wrongSetPrefix(String text) {
+            // do nothing
+        }
+
+        // static
+        public static String getStatic() {
+            return null;
+        }
+    }
+
+    /**
+     * The "get" method has an incorrect argument count.
+     */
+    private static class AnyGetterMismatchParams {
+        @GsonJsonAnyGetter
+        public Map<String, String> getTheMap(String arg) {
+            return new TreeMap<>();
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnyGetter} method.
+     */
+    private static class AnyGetterOnly {
+        @GsonJsonAnyGetter
+        private Map<String, Integer> getOverride() {
+            return null;
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnyGetter} method, but it's ignored.
+     */
+    private static class AnyGetterIgnored {
+        @GsonJsonAnyGetter
+        @GsonJsonIgnore
+        private Map<String, Integer> getOverride() {
+            return null;
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnySetter} method.
+     */
+    private static class AnySetterOnly {
+        @GsonJsonAnySetter
+        private void setOverride(String key, int value) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnySetter} method, but it's ignored.
+     */
+    private static class AnySetterIgnored {
+        @GsonJsonAnySetter
+        @GsonJsonIgnore
+        private void setOverride(String key, int value) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnyGetter} method that overrides the super class' method.
+     */
+    private static class AnyGetterOverride extends DerivedFromData {
+        private Map<String, Integer> overMap;
+
+        @GsonJsonAnyGetter
+        private Map<String, Integer> getOverride() {
+            return overMap;
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnySetter} method that overrides the super class' method.
+     */
+    private static class AnySetterOverride extends DerivedFromData {
+        private Map<String, Integer> overMap;
+
+        @GsonJsonAnySetter
+        private void setOverride(String key, int value) {
+            if (overMap == null) {
+                overMap = new TreeMap<>();
+            }
+
+            overMap.put(key, value);
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnySetter} method with too few parameters.
+     */
+    private static class AnySetterTooFewParams extends DerivedFromData {
+        @GsonJsonAnySetter
+        public void setOverride(String key) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnySetter} method with too few parameters.
+     */
+    private static class AnySetterTooManyParams extends DerivedFromData {
+        @GsonJsonAnySetter
+        public void setOverride(String key, int value, String anotherValue) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Has {@link GsonJsonAnySetter} method whose first argument type is incorrect.
+     */
+    private static class AnySetterInvalidParam extends DerivedFromData {
+        @GsonJsonAnySetter
+        public void setOverride(Integer key, String value) {
+            // do nothing
+        }
+    }
+}
diff --git a/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java b/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java
new file mode 100644
index 0000000..d0f0b1e
--- /dev/null
+++ b/gson/src/test/java/org/onap/policy/common/gson/internal/DataAdapterFactory.java
@@ -0,0 +1,310 @@
+/*
+ * ============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.JsonArray;
+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.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Factory used with test Data.
+ */
+public class DataAdapterFactory implements TypeAdapterFactory {
+
+    /**
+     * Output of {@link #makeList()}, encoded as json.
+     */
+    public static final String ENCODED_LIST = "[{'id':100},{'id':101}]".replace('\'', '"');
+
+    /**
+     * Output of {@link #makeMap()}, encoded as json.
+     */
+    public static final String ENCODED_MAP = "'data-100':{'id':100},'data-101':{'id':101}".replace('\'', '"');
+
+    /**
+     * Object handled by this factory.
+     */
+    public static class Data {
+        private int id;
+
+        public Data() {
+            super();
+        }
+
+        public Data(int id) {
+            this.id = id;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        @Override
+        public String toString() {
+            return "Data [id=" + id + "]";
+        }
+    }
+
+    /**
+     * Object derived from Data.
+     */
+    public static class DerivedData extends Data {
+        private String text;
+
+        public DerivedData() {
+            super();
+        }
+
+        public DerivedData(int id, String text) {
+            super(id);
+            this.text = text;
+        }
+
+        public String getText() {
+            return text;
+        }
+
+        public void setText(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public String toString() {
+            return "DerivedData [text=" + text + ", toString()=" + super.toString() + "]";
+        }
+    }
+
+    /**
+     * Set to {@code true} when {@link #write(JsonWriter, Data)} has been invoked.
+     */
+    private boolean dataWritten = false;
+
+    /**
+     * Set to {@code true} when {@link #read(JsonReader)} has been invoked.
+     */
+    private boolean dataRead = false;
+
+    /**
+     * Clears the flags that indicate that "read" or "write" has been invoked.
+     */
+    public void reset() {
+        dataWritten = true;
+        dataRead = true;
+    }
+
+    public boolean isDataWritten() {
+        return dataWritten;
+    }
+
+    public boolean isDataRead() {
+        return dataRead;
+    }
+
+    /**
+     * Makes a list of Data.
+     *
+     * @return a new list of Data
+     */
+    public static List<Data> makeList() {
+        List<Data> listField = new ArrayList<>();
+
+        listField.add(new Data(100));
+        listField.add(new Data(101));
+
+        return listField;
+    }
+
+    /**
+     * Makes an array of Data.
+     *
+     * @return a new array of Data
+     */
+    public static JsonArray makeArray() {
+        JsonArray arr = new JsonArray();
+
+        for (Data data : makeList()) {
+            JsonObject json = new JsonObject();
+            json.addProperty("id", data.getId());
+            arr.add(json);
+        }
+
+        return arr;
+    }
+
+    /**
+     * Makes a map of Data.
+     *
+     * @return a new map of Data
+     */
+    public static Map<String, List<Data>> makeMap() {
+        Map<String, List<Data>> map = new TreeMap<>();
+
+        for (Data data : makeList()) {
+            map.put("data-" + data.getId(), Arrays.asList(data));
+        }
+
+        return map;
+    }
+
+    /**
+     * Adds Data objects to a tree, mirroring {@link #makeMap()}.
+     *
+     * @param tree tree into which objects are to be added
+     */
+    public static void addToObject(JsonObject tree) {
+        for (JsonElement ent : makeArray()) {
+            JsonObject obj = ent.getAsJsonObject();
+            JsonArray arr = new JsonArray();
+            arr.add(obj);
+            tree.add("data-" + obj.get("id").getAsString(), arr);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        if (type.getRawType() == Data.class) {
+            return (TypeAdapter<T>) new DataTypeAdapter(gson.getDelegateAdapter(this, TypeToken.get(Data.class)),
+                            gson.getAdapter(JsonElement.class));
+        }
+
+        if (type.getRawType() == DerivedData.class) {
+            return (TypeAdapter<T>) new DerivedDataTypeAdapter(
+                            gson.getDelegateAdapter(this, TypeToken.get(DerivedData.class)),
+                            gson.getAdapter(JsonElement.class));
+        }
+
+        return null;
+    }
+
+    /**
+     * Adapter for "Data".
+     */
+    private class DataTypeAdapter extends TypeAdapter<Data> {
+        private TypeAdapter<Data> delegate;
+        private TypeAdapter<JsonElement> elementAdapter;
+
+        /**
+         * Constructs the object.
+         *
+         * @param delegate delegate adapter
+         * @param elementAdapter element adapter
+         */
+        public DataTypeAdapter(TypeAdapter<Data> delegate, TypeAdapter<JsonElement> elementAdapter) {
+            this.delegate = delegate;
+            this.elementAdapter = elementAdapter;
+        }
+
+        @Override
+        public void write(JsonWriter out, Data data) throws IOException {
+            dataWritten = true;
+
+            JsonElement tree = delegate.toJsonTree(data);
+
+            if (tree.isJsonObject()) {
+                JsonObject jsonObj = tree.getAsJsonObject();
+                jsonObj.addProperty("id", data.getId());
+            }
+
+            elementAdapter.write(out, tree);
+        }
+
+        @Override
+        public Data read(JsonReader in) throws IOException {
+            dataRead = true;
+
+            JsonElement tree = elementAdapter.read(in);
+            Data data = delegate.fromJsonTree(tree);
+
+            if (tree.isJsonObject()) {
+                JsonObject jsonObj = tree.getAsJsonObject();
+                data.setId(jsonObj.get("id").getAsInt());
+            }
+
+            return data;
+        }
+    }
+    /**
+     * Adapter for "DerivedData".
+     */
+    private class DerivedDataTypeAdapter extends TypeAdapter<DerivedData> {
+        private TypeAdapter<DerivedData> delegate;
+        private TypeAdapter<JsonElement> elementAdapter;
+
+        /**
+         * Constructs the object.
+         *
+         * @param delegate delegate adapter
+         * @param elementAdapter element adapter
+         */
+        public DerivedDataTypeAdapter(TypeAdapter<DerivedData> delegate, TypeAdapter<JsonElement> elementAdapter) {
+            this.delegate = delegate;
+            this.elementAdapter = elementAdapter;
+        }
+
+        @Override
+        public void write(JsonWriter out, DerivedData data) throws IOException {
+            dataWritten = true;
+
+            JsonElement tree = delegate.toJsonTree(data);
+
+            if (tree.isJsonObject()) {
+                JsonObject jsonObj = tree.getAsJsonObject();
+                jsonObj.addProperty("id", data.getId());
+                jsonObj.addProperty("text", data.getText());
+            }
+
+            elementAdapter.write(out, tree);
+        }
+
+        @Override
+        public DerivedData read(JsonReader in) throws IOException {
+            dataRead = true;
+
+            JsonElement tree = elementAdapter.read(in);
+            DerivedData data = delegate.fromJsonTree(tree);
+
+            if (tree.isJsonObject()) {
+                JsonObject jsonObj = tree.getAsJsonObject();
+                data.setId(jsonObj.get("id").getAsInt());
+                data.setText(jsonObj.get("text").getAsString());
+            }
+
+            return data;
+        }
+    }
+}
diff --git a/policy-endpoints/pom.xml b/policy-endpoints/pom.xml
index 41fa2f3..2c8b0d7 100644
--- a/policy-endpoints/pom.xml
+++ b/policy-endpoints/pom.xml
@@ -53,6 +53,12 @@
 
         <dependency>
             <groupId>org.onap.policy.common</groupId>
+            <artifactId>gson</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
             <artifactId>utils</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java
index 037f6c6..286d73d 100644
--- a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/http/server/test/MyGsonProvider.java
@@ -27,7 +27,7 @@
 import java.lang.reflect.Type;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
-import org.onap.policy.common.endpoints.http.server.internal.GsonMessageBodyHandler;
+import org.onap.policy.common.gson.GsonMessageBodyHandler;
 
 /**
  * GsonMessageBodyHandler that tracks activities.
diff --git a/pom.xml b/pom.xml
index e3b1ae8..de4f6a9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,8 +1,8 @@
-<!-- 
-  ============LICENSE_START======================================================= 
-  ONAP policy 
+<!--
+  ============LICENSE_START=======================================================
+  ONAP policy
   ================================================================================
-  Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+  Copyright (C) 2017-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.
@@ -15,7 +15,7 @@
   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========================================================= 
+  ============LICENSE_END=========================================================
 -->
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
@@ -51,7 +51,7 @@
         <staging.path>content/repositories/staging/</staging.path>
 
         <!-- sonar/jacoco overrides -->
-        <!-- Overriding oparent default sonar/jacoco settings Combine all 
+        <!-- Overriding oparent default sonar/jacoco settings Combine all
             our reports into one file shared across sub-modules -->
         <sonar.jacoco.reportPath>${project.basedir}/../target/code-coverage/jacoco-ut.exec</sonar.jacoco.reportPath>
         <sonar.jacoco.itReportPath>${project.basedir}/../target/code-coverage/jacoco-it.exec</sonar.jacoco.itReportPath>
@@ -65,6 +65,7 @@
         <module>capabilities</module>
         <module>utils-test</module>
         <module>utils</module>
+        <module>gson</module>
         <module>common-logging</module>
         <module>common-parameters</module>
         <module>integrity-audit</module>
@@ -90,7 +91,7 @@
                     <artifactId>jacoco-maven-plugin</artifactId>
                     <version>${jacoco.version}</version>
                     <configuration>
-                        <!-- Note: This exclusion list should match <sonar.exclusions> 
+                        <!-- Note: This exclusion list should match <sonar.exclusions>
                             property above -->
                         <excludes>
                             <exclude>**/gen/**</exclude>
@@ -100,8 +101,8 @@
                         </excludes>
                     </configuration>
                     <executions>
-                        <!-- Prepares the property pointing to the JaCoCo 
-                            runtime agent which is passed as VM argument when Maven the Surefire plugin 
+                        <!-- Prepares the property pointing to the JaCoCo
+                            runtime agent which is passed as VM argument when Maven the Surefire plugin
                             is executed. -->
                         <execution>
                             <id>pre-unit-test</id>
@@ -112,7 +113,7 @@
                                 <destFile>${sonar.jacoco.reportPath}</destFile>
                             </configuration>
                         </execution>
-                        <!-- Ensures that the code coverage report for unit 
+                        <!-- Ensures that the code coverage report for unit
                             tests is created after unit tests have been run. -->
                         <execution>
                             <id>post-unit-test</id>