Back to Repositories

Testing Circular Reference Handling in GSON

This test suite examines GSON’s handling of circular references during JSON serialization and deserialization. It validates how the library manages self-referential objects and cyclic dependencies to prevent infinite recursion and stack overflow errors.

Test Coverage Overview

The test suite provides comprehensive coverage of circular reference scenarios in JSON processing:

  • Direct self-references in object properties
  • Nested circular references through collections
  • Array-based self-references
  • Custom serialization handling of circular dependencies
  • Directed acyclic graph serialization and deserialization

Implementation Analysis

The testing approach employs JUnit to validate GSON’s circular reference handling mechanisms. It uses carefully constructed test cases with self-referential objects and nested object graphs to verify proper serialization behavior and error handling.

The implementation leverages both standard GSON APIs and custom serializers to test different aspects of circular reference detection.

Technical Details

  • Testing Framework: JUnit
  • Assertion Libraries: Google Truth
  • Key Classes: Gson, GsonBuilder, JsonSerializer
  • Test Fixtures: Custom classes with self-referential structures
  • Error Validation: StackOverflowError assertions

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases for different circular reference scenarios
  • Proper exception testing using assertThrows
  • Clear test method naming conventions
  • Comprehensive edge case coverage
  • Setup method usage for test initialization

google/gson

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

            
/*
 * Copyright (C) 2008 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.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.ClassOverridingEquals;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;

/**
 * Functional tests related to circular reference detection and error reporting.
 *
 * @author Inderjeet Singh
 * @author Joel Leitch
 */
public class CircularReferenceTest {
  private Gson gson;

  @Before
  public void setUp() throws Exception {
    gson = new Gson();
  }

  @Test
  public void testCircularSerialization() {
    ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
    ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
    a.children.add(b);
    b.children.add(a);
    // Circular types should not get printed
    assertThrows(StackOverflowError.class, () -> gson.toJson(a));
  }

  @Test
  public void testSelfReferenceIgnoredInSerialization() {
    ClassOverridingEquals objA = new ClassOverridingEquals();
    objA.ref = objA;

    String json = gson.toJson(objA);
    assertThat(json).doesNotContain("ref"); // self-reference is ignored
  }

  @Test
  public void testSelfReferenceArrayFieldSerialization() {
    ClassWithSelfReferenceArray objA = new ClassWithSelfReferenceArray();
    objA.children = new ClassWithSelfReferenceArray[] {objA};

    // Circular reference to self can not be serialized
    assertThrows(StackOverflowError.class, () -> gson.toJson(objA));
  }

  @Test
  public void testSelfReferenceCustomHandlerSerialization() {
    ClassWithSelfReference obj = new ClassWithSelfReference();
    obj.child = obj;
    Gson gson =
        new GsonBuilder()
            .registerTypeAdapter(
                ClassWithSelfReference.class,
                new JsonSerializer<ClassWithSelfReference>() {
                  @Override
                  public JsonElement serialize(
                      ClassWithSelfReference src,
                      Type typeOfSrc,
                      JsonSerializationContext context) {
                    JsonObject obj = new JsonObject();
                    obj.addProperty("property", "value");
                    obj.add("child", context.serialize(src.child));
                    return obj;
                  }
                })
            .create();

    // Circular reference to self can not be serialized
    assertThrows(StackOverflowError.class, () -> gson.toJson(obj));
  }

  @Test
  public void testDirectedAcyclicGraphSerialization() {
    ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
    ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
    ContainsReferenceToSelfType c = new ContainsReferenceToSelfType();
    a.children.add(b);
    a.children.add(c);
    b.children.add(c);
    assertThat(gson.toJson(a)).isNotNull();
  }

  @Test
  public void testDirectedAcyclicGraphDeserialization() {
    String json = "{\"children\":[{\"children\":[{\"children\":[]}]},{\"children\":[]}]}";
    ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
    assertThat(target).isNotNull();
    assertThat(target.children).hasSize(2);
  }

  private static class ContainsReferenceToSelfType {
    Collection<ContainsReferenceToSelfType> children = new ArrayList<>();
  }

  private static class ClassWithSelfReference {
    ClassWithSelfReference child;
  }

  private static class ClassWithSelfReferenceArray {
    @SuppressWarnings("unused")
    ClassWithSelfReferenceArray[] children;
  }
}