Back to Repositories

Testing Complex Map Serialization Adapters in Google GSON

This test suite validates GSON’s capability to handle complex map serialization and deserialization, particularly focusing on maps with custom object keys and type adapters. The tests verify proper JSON conversion of map structures with various key-value combinations and type configurations.

Test Coverage Overview

The test suite provides comprehensive coverage of map serialization scenarios in GSON.

Key areas tested include:
  • Complex map key serialization with custom objects
  • Type adapter interactions with different map configurations
  • Handling of duplicate keys and type collisions
  • Generic type variable support in map structures

Implementation Analysis

The testing approach uses JUnit to validate GSON’s MapTypeAdapter functionality through various test cases. The implementation leverages TypeToken for generic type handling and GsonBuilder for custom configurations.

Notable patterns include:
  • Builder pattern usage for GSON configuration
  • Type token implementation for generic type preservation
  • Custom object equality testing

Technical Details

Testing infrastructure includes:
  • JUnit 4 test framework
  • GSON library with complex map serialization enabled
  • Custom Point class for object key testing
  • TypeToken for generic type resolution
  • Truth assertion library for enhanced verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases for specific functionality
  • Comprehensive edge case coverage
  • Clear test method naming conventions
  • Proper test setup and configuration
  • Effective use of assertions for validation

google/gson

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

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

package com.google.gson.functional;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;

public class MapAsArrayTypeAdapterTest {

  @Test
  public void testSerializeComplexMapWithTypeAdapter() {
    Type type = new TypeToken<Map<Point, String>>() {}.getType();
    Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();

    Map<Point, String> original = new LinkedHashMap<>();
    original.put(new Point(5, 5), "a");
    original.put(new Point(8, 8), "b");
    String json = gson.toJson(original, type);
    assertThat(json).isEqualTo("[[{\"x\":5,\"y\":5},\"a\"],[{\"x\":8,\"y\":8},\"b\"]]");
    assertThat(gson.<Map<Point, String>>fromJson(json, type)).isEqualTo(original);

    // test that registering a type adapter for one map doesn't interfere with others
    Map<String, Boolean> otherMap = new LinkedHashMap<>();
    otherMap.put("t", true);
    otherMap.put("f", false);
    assertThat(gson.toJson(otherMap, Map.class)).isEqualTo("{\"t\":true,\"f\":false}");
    assertThat(gson.toJson(otherMap, new TypeToken<Map<String, Boolean>>() {}.getType()))
        .isEqualTo("{\"t\":true,\"f\":false}");
    assertThat(
            gson.<Object>fromJson(
                "{\"t\":true,\"f\":false}", new TypeToken<Map<String, Boolean>>() {}.getType()))
        .isEqualTo(otherMap);
  }

  @Test
  @Ignore("we no longer hash keys at serialization time")
  public void testTwoTypesCollapseToOneSerialize() {
    Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();

    Map<Number, String> original = new LinkedHashMap<>();
    original.put(1.0D, "a");
    original.put(1.0F, "b");
    Type type = new TypeToken<Map<Number, String>>() {}.getType();
    var e = assertThrows(JsonSyntaxException.class, () -> gson.toJson(original, type));
    assertThat(e).hasMessageThat().isEqualTo("TODO");
  }

  @Test
  public void testTwoTypesCollapseToOneDeserialize() {
    Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();

    String s = "[[\"1.00\",\"a\"],[\"1.0\",\"b\"]]";
    Type type = new TypeToken<Map<Double, String>>() {}.getType();
    var e = assertThrows(JsonSyntaxException.class, () -> gson.fromJson(s, type));
    assertThat(e).hasMessageThat().isEqualTo("duplicate key: 1.0");
  }

  @Test
  public void testMultipleEnableComplexKeyRegistrationHasNoEffect() {
    Type type = new TypeToken<Map<Point, String>>() {}.getType();
    Gson gson =
        new GsonBuilder()
            .enableComplexMapKeySerialization()
            .enableComplexMapKeySerialization()
            .create();

    Map<Point, String> original = new LinkedHashMap<>();
    original.put(new Point(6, 5), "abc");
    original.put(new Point(1, 8), "def");
    String json = gson.toJson(original, type);
    assertThat(json).isEqualTo("[[{\"x\":6,\"y\":5},\"abc\"],[{\"x\":1,\"y\":8},\"def\"]]");
    assertThat(gson.<Map<Point, String>>fromJson(json, type)).isEqualTo(original);
  }

  @Test
  public void testMapWithTypeVariableSerialization() {
    Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
    PointWithProperty<Point> map = new PointWithProperty<>();
    map.map.put(new Point(2, 3), new Point(4, 5));
    Type type = new TypeToken<PointWithProperty<Point>>() {}.getType();
    String json = gson.toJson(map, type);
    assertThat(json).isEqualTo("{\"map\":[[{\"x\":2,\"y\":3},{\"x\":4,\"y\":5}]]}");
  }

  @Test
  public void testMapWithTypeVariableDeserialization() {
    Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
    String json = "{map:[[{x:2,y:3},{x:4,y:5}]]}";
    Type type = new TypeToken<PointWithProperty<Point>>() {}.getType();
    PointWithProperty<Point> map = gson.fromJson(json, type);
    Point key = map.map.keySet().iterator().next();
    Point value = map.map.values().iterator().next();
    assertThat(key).isEqualTo(new Point(2, 3));
    assertThat(value).isEqualTo(new Point(4, 5));
  }

  static class Point {
    int x;
    int y;

    Point(int x, int y) {
      this.x = x;
      this.y = y;
    }

    Point() {}

    @Override
    public boolean equals(Object o) {
      return o instanceof Point && ((Point) o).x == x && ((Point) o).y == y;
    }

    @Override
    public int hashCode() {
      return x * 37 + y;
    }

    @Override
    public String toString() {
      return "(" + x + "," + y + ")";
    }
  }

  static class PointWithProperty<T> {
    Map<Point, T> map = new HashMap<>();
  }
}