Back to Repositories

Testing Type Hierarchy Adapter Implementation in google/gson

This test suite validates GSON’s type hierarchy adapter functionality, specifically testing JSON serialization and deserialization for complex object hierarchies with inheritance relationships.

Test Coverage Overview

The test suite provides comprehensive coverage of type hierarchy handling in GSON, focusing on employee-manager-CEO inheritance chains. Key test cases include:
  • Complex object serialization with nested hierarchies
  • Proper handling of inheritance relationships
  • Adapter registration order scenarios
  • Bidirectional conversion verification

Implementation Analysis

The testing approach implements custom type adapters for handling class hierarchies, demonstrating GSON’s flexibility with complex object structures. The implementation showcases:
  • Custom JsonSerializer and JsonDeserializer implementations
  • Type hierarchy adapter registration patterns
  • Nested object serialization handling

Technical Details

Testing infrastructure includes:
  • JUnit test framework
  • GSON Builder configuration
  • Custom type adapters
  • Truth assertion library for validation
  • Hierarchical class structure with Employee, Manager, and CEO classes

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Comprehensive assertion validation
  • Clear test case organization
  • Edge case handling for inheritance scenarios
  • Proper test isolation and setup
  • Effective use of test data structures

google/gson

gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java

            
/*
 * Copyright (C) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.gson.functional;

import static com.google.common.truth.Truth.assertThat;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import org.junit.Test;

/** Test that the hierarchy adapter works when subtypes are used. */
public final class TypeHierarchyAdapterTest {

  @Test
  public void testTypeHierarchy() {
    Manager andy = new Manager();
    andy.userid = "andy";
    andy.startDate = 2005;
    andy.minions =
        new Employee[] {
          new Employee("inder", 2007), new Employee("joel", 2006), new Employee("jesse", 2006),
        };

    CEO eric = new CEO();
    eric.userid = "eric";
    eric.startDate = 2001;
    eric.assistant = new Employee("jerome", 2006);

    eric.minions =
        new Employee[] {
          new Employee("larry", 1998), new Employee("sergey", 1998), andy,
        };

    Gson gson =
        new GsonBuilder()
            .registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
            .setPrettyPrinting()
            .create();

    Company company = new Company();
    company.ceo = eric;

    String json = gson.toJson(company, Company.class);
    assertThat(json)
        .isEqualTo(
            "{\n"
                + "  \"ceo\": {\n"
                + "    \"userid\": \"eric\",\n"
                + "    \"startDate\": 2001,\n"
                + "    \"minions\": [\n"
                + "      {\n"
                + "        \"userid\": \"larry\",\n"
                + "        \"startDate\": 1998\n"
                + "      },\n"
                + "      {\n"
                + "        \"userid\": \"sergey\",\n"
                + "        \"startDate\": 1998\n"
                + "      },\n"
                + "      {\n"
                + "        \"userid\": \"andy\",\n"
                + "        \"startDate\": 2005,\n"
                + "        \"minions\": [\n"
                + "          {\n"
                + "            \"userid\": \"inder\",\n"
                + "            \"startDate\": 2007\n"
                + "          },\n"
                + "          {\n"
                + "            \"userid\": \"joel\",\n"
                + "            \"startDate\": 2006\n"
                + "          },\n"
                + "          {\n"
                + "            \"userid\": \"jesse\",\n"
                + "            \"startDate\": 2006\n"
                + "          }\n"
                + "        ]\n"
                + "      }\n"
                + "    ],\n"
                + "    \"assistant\": {\n"
                + "      \"userid\": \"jerome\",\n"
                + "      \"startDate\": 2006\n"
                + "    }\n"
                + "  }\n"
                + "}");

    Company copied = gson.fromJson(json, Company.class);
    assertThat(gson.toJson(copied, Company.class)).isEqualTo(json);
    assertThat(company.ceo.userid).isEqualTo(copied.ceo.userid);
    assertThat(company.ceo.assistant.userid).isEqualTo(copied.ceo.assistant.userid);
    assertThat(company.ceo.minions[0].userid).isEqualTo(copied.ceo.minions[0].userid);
    assertThat(company.ceo.minions[1].userid).isEqualTo(copied.ceo.minions[1].userid);
    assertThat(company.ceo.minions[2].userid).isEqualTo(copied.ceo.minions[2].userid);
    assertThat(((Manager) company.ceo.minions[2]).minions[0].userid)
        .isEqualTo(((Manager) copied.ceo.minions[2]).minions[0].userid);
    assertThat(((Manager) company.ceo.minions[2]).minions[1].userid)
        .isEqualTo(((Manager) copied.ceo.minions[2]).minions[1].userid);
  }

  @Test
  public void testRegisterSuperTypeFirst() {
    Gson gson =
        new GsonBuilder()
            .registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
            .registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
            .create();

    Manager manager = new Manager();
    manager.userid = "inder";

    String json = gson.toJson(manager, Manager.class);
    assertThat(json).isEqualTo("\"inder\"");
    Manager copied = gson.fromJson(json, Manager.class);
    assertThat(copied.userid).isEqualTo(manager.userid);
  }

  /** This behaviour changed in Gson 2.1; it used to throw. */
  @Test
  public void testRegisterSubTypeFirstAllowed() {
    Gson unused =
        new GsonBuilder()
            .registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
            .registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
            .create();
  }

  static class ManagerAdapter implements JsonSerializer<Manager>, JsonDeserializer<Manager> {
    @Override
    public Manager deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
      Manager result = new Manager();
      result.userid = json.getAsString();
      return result;
    }

    @Override
    public JsonElement serialize(Manager src, Type typeOfSrc, JsonSerializationContext context) {
      return new JsonPrimitive(src.userid);
    }
  }

  static class EmployeeAdapter implements JsonSerializer<Employee>, JsonDeserializer<Employee> {
    @Override
    public JsonElement serialize(
        Employee employee, Type typeOfSrc, JsonSerializationContext context) {
      JsonObject result = new JsonObject();
      result.add("userid", context.serialize(employee.userid, String.class));
      result.add("startDate", context.serialize(employee.startDate, long.class));
      if (employee instanceof Manager) {
        result.add("minions", context.serialize(((Manager) employee).minions, Employee[].class));
        if (employee instanceof CEO) {
          result.add("assistant", context.serialize(((CEO) employee).assistant, Employee.class));
        }
      }
      return result;
    }

    @Override
    public Employee deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
        throws JsonParseException {
      JsonObject object = json.getAsJsonObject();
      Employee result = null;

      // if the employee has an assistant, she must be the CEO
      JsonElement assistant = object.get("assistant");
      if (assistant != null) {
        result = new CEO();
        ((CEO) result).assistant = context.deserialize(assistant, Employee.class);
      }

      // only managers have minions
      JsonElement minons = object.get("minions");
      if (minons != null) {
        if (result == null) {
          result = new Manager();
        }
        ((Manager) result).minions = context.deserialize(minons, Employee[].class);
      }

      if (result == null) {
        result = new Employee();
      }
      result.userid = context.deserialize(object.get("userid"), String.class);
      result.startDate = context.deserialize(object.get("startDate"), long.class);
      return result;
    }
  }

  static class Employee {
    String userid;
    long startDate;

    Employee(String userid, long startDate) {
      this.userid = userid;
      this.startDate = startDate;
    }

    Employee() {}
  }

  static class Manager extends Employee {
    Employee[] minions;
  }

  @SuppressWarnings("MemberName")
  static class CEO extends Manager {
    Employee assistant;
  }

  static class Company {
    CEO ceo;
  }
}