Back to Repositories

Testing GSON Object Serialization and Deserialization Workflows in google/gson

This test suite validates core JSON serialization and deserialization functionality in the Google GSON library. It covers basic object mapping, primitive handling, nested objects, arrays, and edge cases to ensure robust JSON processing.

Test Coverage Overview

Comprehensive test coverage for JSON serialization and deserialization of Java objects using GSON. Tests include primitive type handling, object nesting, array processing, null values, and custom field handling. Key edge cases covered include empty collections, malformed JSON, and deeply nested structures.

  • Basic object serialization/deserialization
  • Primitive type handling and arrays
  • Nested object structures
  • Null value processing
  • Collection and array handling

Implementation Analysis

The test suite employs JUnit for structured testing with GSON’s core APIs. Implementation focuses on validating object mapping accuracy, type conversion, and error handling. Uses setUp/tearDown for consistent test environments and leverages Truth assertions for validation.

  • JUnit test framework integration
  • GSON builder configuration
  • Custom type adapters
  • Exception validation

Technical Details

Testing tools and configuration:

  • JUnit 4 test framework
  • Google Truth assertion library
  • GSON 2.x library
  • Custom test types and utility classes
  • TimeZone and Locale configuration

Best Practices Demonstrated

The test suite showcases several testing best practices including proper test isolation, comprehensive edge case coverage, and clear test organization. Each test focuses on a specific functionality with clear setup and validation.

  • Isolated test cases
  • Comprehensive assertion coverage
  • Clear test naming and organization
  • Proper test cleanup

google/gson

gson/src/test/java/com/google/gson/functional/ObjectTest.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.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException;
import com.google.gson.common.TestTypes.ArrayOfObjects;
import com.google.gson.common.TestTypes.BagOfPrimitiveWrappers;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassWithArray;
import com.google.gson.common.TestTypes.ClassWithNoFields;
import com.google.gson.common.TestTypes.ClassWithObjects;
import com.google.gson.common.TestTypes.ClassWithTransientFields;
import com.google.gson.common.TestTypes.Nested;
import com.google.gson.common.TestTypes.PrimitiveArray;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Functional tests for Json serialization and deserialization of regular classes.
 *
 * @author Inderjeet Singh
 * @author Joel Leitch
 */
public class ObjectTest {
  private Gson gson;
  private TimeZone oldTimeZone;
  private Locale oldLocale;

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

    oldTimeZone = TimeZone.getDefault();
    TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
    oldLocale = Locale.getDefault();
    Locale.setDefault(Locale.US);
  }

  @After
  public void tearDown() {
    TimeZone.setDefault(oldTimeZone);
    Locale.setDefault(oldLocale);
  }

  @Test
  public void testJsonInSingleQuotesDeserialization() {
    String json = "{'stringValue':'no message','intValue':10,'longValue':20}";
    BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(target.stringValue).isEqualTo("no message");
    assertThat(target.intValue).isEqualTo(10);
    assertThat(target.longValue).isEqualTo(20);
  }

  @Test
  public void testJsonInMixedQuotesDeserialization() {
    String json = "{\"stringValue\":'no message','intValue':10,'longValue':20}";
    BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(target.stringValue).isEqualTo("no message");
    assertThat(target.intValue).isEqualTo(10);
    assertThat(target.longValue).isEqualTo(20);
  }

  @Test
  public void testBagOfPrimitivesSerialization() {
    BagOfPrimitives target = new BagOfPrimitives(10, 20, false, "stringValue");
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testBagOfPrimitivesDeserialization() {
    BagOfPrimitives src = new BagOfPrimitives(10, 20, false, "stringValue");
    String json = src.getExpectedJson();
    BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  @Test
  public void testBagOfPrimitiveWrappersSerialization() {
    BagOfPrimitiveWrappers target = new BagOfPrimitiveWrappers(10L, 20, false);
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testBagOfPrimitiveWrappersDeserialization() {
    BagOfPrimitiveWrappers target = new BagOfPrimitiveWrappers(10L, 20, false);
    String jsonString = target.getExpectedJson();
    target = gson.fromJson(jsonString, BagOfPrimitiveWrappers.class);
    assertThat(target.getExpectedJson()).isEqualTo(jsonString);
  }

  @Test
  public void testClassWithTransientFieldsSerialization() {
    ClassWithTransientFields<Long> target = new ClassWithTransientFields<>(1L);
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testClassWithTransientFieldsDeserialization() {
    String json = "{\"longValue\":[1]}";
    ClassWithTransientFields<?> target = gson.fromJson(json, ClassWithTransientFields.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  @Test
  public void testClassWithTransientFieldsDeserializationTransientFieldsPassedInJsonAreIgnored() {
    String json = "{\"transientLongValue\":5,\"longValue\":[1]}";
    ClassWithTransientFields<?> target = gson.fromJson(json, ClassWithTransientFields.class);
    assertThat(target.transientLongValue).isEqualTo(1);
  }

  @Test
  public void testClassWithNoFieldsSerialization() {
    assertThat(gson.toJson(new ClassWithNoFields())).isEqualTo("{}");
  }

  @Test
  public void testClassWithNoFieldsDeserialization() {
    String json = "{}";
    ClassWithNoFields target = gson.fromJson(json, ClassWithNoFields.class);
    ClassWithNoFields expected = new ClassWithNoFields();
    assertThat(target).isEqualTo(expected);
  }

  private static class Subclass extends Superclass1 {}

  private static class Superclass1 extends Superclass2 {
    @SuppressWarnings({"unused", "HidingField"})
    String s;
  }

  private static class Superclass2 {
    @SuppressWarnings("unused")
    String s;
  }

  @Test
  public void testClassWithDuplicateFields() {
    String expectedMessage =
        "Class com.google.gson.functional.ObjectTest$Subclass declares multiple JSON fields named"
            + " 's'; conflict is caused by fields"
            + " com.google.gson.functional.ObjectTest$Superclass1#s and"
            + " com.google.gson.functional.ObjectTest$Superclass2#s\n"
            + "See https://github.com/google/gson/blob/main/Troubleshooting.md#duplicate-fields";

    var e = assertThrows(IllegalArgumentException.class, () -> gson.getAdapter(Subclass.class));
    assertThat(e).hasMessageThat().isEqualTo(expectedMessage);

    // Detection should also work properly when duplicate fields exist only for serialization
    Gson gson =
        new GsonBuilder()
            .addDeserializationExclusionStrategy(
                new ExclusionStrategy() {
                  @Override
                  public boolean shouldSkipField(FieldAttributes f) {
                    // Skip all fields for deserialization
                    return true;
                  }

                  @Override
                  public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                  }
                })
            .create();

    e = assertThrows(IllegalArgumentException.class, () -> gson.getAdapter(Subclass.class));
    assertThat(e).hasMessageThat().isEqualTo(expectedMessage);
  }

  @Test
  public void testNestedSerialization() {
    Nested target =
        new Nested(
            new BagOfPrimitives(10, 20, false, "stringValue"),
            new BagOfPrimitives(30, 40, true, "stringValue"));
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testNestedDeserialization() {
    String json =
        "{\"primitive1\":{\"longValue\":10,\"intValue\":20,\"booleanValue\":false,"
            + "\"stringValue\":\"stringValue\"},\"primitive2\":{\"longValue\":30,\"intValue\":40,"
            + "\"booleanValue\":true,\"stringValue\":\"stringValue\"}}";
    Nested target = gson.fromJson(json, Nested.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  @Test
  public void testNullSerialization() {
    assertThat(gson.toJson(null)).isEqualTo("null");
  }

  @Test
  public void testEmptyStringDeserialization() {
    Object object = gson.fromJson("", Object.class);
    assertThat(object).isNull();
  }

  @Test
  public void testTruncatedDeserialization() {
    Type type = new TypeToken<List<String>>() {}.getType();
    var e = assertThrows(JsonParseException.class, () -> gson.fromJson("[\"a\", \"b\",", type));
    assertThat(e).hasCauseThat().isInstanceOf(EOFException.class);
  }

  @Test
  public void testNullDeserialization() {
    String myNullObject = null;
    Object object = gson.fromJson(myNullObject, Object.class);
    assertThat(object).isNull();
  }

  @Test
  public void testNullFieldsSerialization() {
    Nested target = new Nested(new BagOfPrimitives(10, 20, false, "stringValue"), null);
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testNullFieldsDeserialization() {
    String json =
        "{\"primitive1\":{\"longValue\":10,\"intValue\":20,\"booleanValue\":false"
            + ",\"stringValue\":\"stringValue\"}}";
    Nested target = gson.fromJson(json, Nested.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  @Test
  public void testArrayOfObjectsSerialization() {
    ArrayOfObjects target = new ArrayOfObjects();
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testArrayOfObjectsDeserialization() {
    String json = new ArrayOfObjects().getExpectedJson();
    ArrayOfObjects target = gson.fromJson(json, ArrayOfObjects.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  @Test
  public void testArrayOfArraysSerialization() {
    ArrayOfArrays target = new ArrayOfArrays();
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  @Test
  public void testArrayOfArraysDeserialization() {
    String json = new ArrayOfArrays().getExpectedJson();
    ArrayOfArrays target = gson.fromJson(json, ArrayOfArrays.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  @Test
  public void testArrayOfObjectsAsFields() {
    ClassWithObjects classWithObjects = new ClassWithObjects();
    BagOfPrimitives bagOfPrimitives = new BagOfPrimitives();
    String stringValue = "someStringValueInArray";
    String classWithObjectsJson = gson.toJson(classWithObjects);
    String bagOfPrimitivesJson = gson.toJson(bagOfPrimitives);

    ClassWithArray classWithArray =
        new ClassWithArray(new Object[] {stringValue, classWithObjects, bagOfPrimitives});
    String json = gson.toJson(classWithArray);

    assertThat(json).contains(classWithObjectsJson);
    assertThat(json).contains(bagOfPrimitivesJson);
    assertThat(json).contains("\"" + stringValue + "\"");
  }

  /** Created in response to Issue 14: http://code.google.com/p/google-gson/issues/detail?id=14 */
  @Test
  public void testNullArraysDeserialization() {
    String json = "{\"array\": null}";
    ClassWithArray target = gson.fromJson(json, ClassWithArray.class);
    assertThat(target.array).isNull();
  }

  /** Created in response to Issue 14: http://code.google.com/p/google-gson/issues/detail?id=14 */
  @Test
  public void testNullObjectFieldsDeserialization() {
    String json = "{\"bag\": null}";
    ClassWithObjects target = gson.fromJson(json, ClassWithObjects.class);
    assertThat(target.bag).isNull();
  }

  @Test
  public void testEmptyCollectionInAnObjectDeserialization() {
    String json = "{\"children\":[]}";
    ClassWithCollectionField target = gson.fromJson(json, ClassWithCollectionField.class);
    assertThat(target).isNotNull();
    assertThat(target.children).isEmpty();
  }

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

  @Test
  public void testPrimitiveArrayInAnObjectDeserialization() {
    String json = "{\"longArray\":[0,1,2,3,4,5,6,7,8,9]}";
    PrimitiveArray target = gson.fromJson(json, PrimitiveArray.class);
    assertThat(target.getExpectedJson()).isEqualTo(json);
  }

  /** Created in response to Issue 14: http://code.google.com/p/google-gson/issues/detail?id=14 */
  @Test
  public void testNullPrimitiveFieldsDeserialization() {
    String json = "{\"longValue\":null}";
    BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(target.longValue).isEqualTo(BagOfPrimitives.DEFAULT_VALUE);
  }

  @Test
  public void testEmptyCollectionInAnObjectSerialization() {
    ClassWithCollectionField target = new ClassWithCollectionField();
    assertThat(gson.toJson(target)).isEqualTo("{\"children\":[]}");
  }

  @Test
  public void testPrivateNoArgConstructorDeserialization() {
    ClassWithPrivateNoArgsConstructor target =
        gson.fromJson("{\"a\":20}", ClassWithPrivateNoArgsConstructor.class);
    assertThat(target.a).isEqualTo(20);
  }

  @Test
  public void testAnonymousLocalClassesSerialization() {
    assertThat(
            gson.toJson(
                new ClassWithNoFields() {
                  // empty anonymous class
                }))
        .isEqualTo("null");

    class Local {}
    assertThat(gson.toJson(new Local())).isEqualTo("null");
  }

  @Test
  public void testAnonymousLocalClassesCustomSerialization() {
    Gson gson =
        new GsonBuilder()
            .registerTypeHierarchyAdapter(
                ClassWithNoFields.class,
                (JsonSerializer<ClassWithNoFields>)
                    (src, typeOfSrc, context) -> new JsonPrimitive("custom-value"))
            .create();

    assertThat(
            gson.toJson(
                new ClassWithNoFields() {
                  // empty anonymous class
                }))
        .isEqualTo("\"custom-value\"");

    class Local {}
    gson =
        new GsonBuilder()
            .registerTypeAdapter(
                Local.class,
                (JsonSerializer<Local>)
                    (src, typeOfSrc, context) -> new JsonPrimitive("custom-value"))
            .create();
    assertThat(gson.toJson(new Local())).isEqualTo("\"custom-value\"");
  }

  @Test
  public void testAnonymousLocalClassesCustomDeserialization() {
    Gson gson =
        new GsonBuilder()
            .registerTypeHierarchyAdapter(
                ClassWithNoFields.class,
                (JsonDeserializer<ClassWithNoFields>)
                    (json, typeOfT, context) -> new ClassWithNoFields())
            .create();

    assertThat(gson.fromJson("{}", ClassWithNoFields.class)).isNotNull();
    Class<?> anonymousClass = new ClassWithNoFields() {}.getClass();
    // Custom deserializer is ignored
    assertThat(gson.fromJson("{}", anonymousClass)).isNull();

    class Local {}
    gson =
        new GsonBuilder()
            .registerTypeAdapter(
                Local.class,
                (JsonDeserializer<Local>)
                    (json, typeOfT, context) -> {
                      throw new AssertionError("should not be called");
                    })
            .create();
    // Custom deserializer is ignored
    assertThat(gson.fromJson("{}", Local.class)).isNull();
  }

  @Test
  public void testPrimitiveArrayFieldSerialization() {
    PrimitiveArray target = new PrimitiveArray(new long[] {1L, 2L, 3L});
    assertThat(gson.toJson(target)).isEqualTo(target.getExpectedJson());
  }

  /** Tests that a class field with type Object can be serialized properly. See issue 54 */
  @Test
  public void testClassWithObjectFieldSerialization() {
    ClassWithObjectField obj = new ClassWithObjectField();
    obj.member = "abc";
    String json = gson.toJson(obj);
    assertThat(json).contains("abc");
  }

  private static class ClassWithObjectField {
    @SuppressWarnings("unused")
    Object member;
  }

  @Test
  public void testInnerClassSerialization() {
    Parent p = new Parent();
    Parent.Child c = p.new Child();
    String json = gson.toJson(c);
    assertThat(json).contains("value2");
    assertThat(json).doesNotContain("value1");
  }

  @Test
  public void testInnerClassDeserialization() {
    Parent p = new Parent();
    Gson gson =
        new GsonBuilder()
            .registerTypeAdapter(
                Parent.Child.class,
                new InstanceCreator<Parent.Child>() {
                  @Override
                  public Parent.Child createInstance(Type type) {
                    return p.new Child();
                  }
                })
            .create();
    String json = "{'value2':3}";
    Parent.Child c = gson.fromJson(json, Parent.Child.class);
    assertThat(c.value2).isEqualTo(3);
  }

  private static class Parent {
    @SuppressWarnings("unused")
    int value1 = 1;

    @SuppressWarnings("ClassCanBeStatic")
    private class Child {
      int value2 = 2;
    }
  }

  private static class ArrayOfArrays {
    private final BagOfPrimitives[][] elements;

    public ArrayOfArrays() {
      elements = new BagOfPrimitives[3][2];
      for (int i = 0; i < elements.length; ++i) {
        BagOfPrimitives[] row = elements[i];
        for (int j = 0; j < row.length; ++j) {
          row[j] = new BagOfPrimitives(i + j, i * j, false, i + "_" + j);
        }
      }
    }

    public String getExpectedJson() {
      StringBuilder sb = new StringBuilder("{\"elements\":[");
      boolean first = true;
      for (BagOfPrimitives[] row : elements) {
        if (first) {
          first = false;
        } else {
          sb.append(",");
        }
        boolean firstOfRow = true;
        sb.append("[");
        for (BagOfPrimitives element : row) {
          if (firstOfRow) {
            firstOfRow = false;
          } else {
            sb.append(",");
          }
          sb.append(element.getExpectedJson());
        }
        sb.append("]");
      }
      sb.append("]}");
      return sb.toString();
    }
  }

  private static class ClassWithPrivateNoArgsConstructor {
    public int a;

    private ClassWithPrivateNoArgsConstructor() {
      a = 10;
    }
  }

  /** In response to Issue 41 http://code.google.com/p/google-gson/issues/detail?id=41 */
  @Test
  public void testObjectFieldNamesWithoutQuotesDeserialization() {
    String json = "{longValue:1,'booleanValue':true,\"stringValue\":'bar'}";
    BagOfPrimitives bag = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(bag.longValue).isEqualTo(1);
    assertThat(bag.booleanValue).isTrue();
    assertThat(bag.stringValue).isEqualTo("bar");
  }

  @Test
  public void testStringFieldWithNumberValueDeserialization() {
    String json = "{\"stringValue\":1}";
    BagOfPrimitives bag = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(bag.stringValue).isEqualTo("1");

    json = "{\"stringValue\":1.5E+6}";
    bag = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(bag.stringValue).isEqualTo("1.5E+6");

    json = "{\"stringValue\":true}";
    bag = gson.fromJson(json, BagOfPrimitives.class);
    assertThat(bag.stringValue).isEqualTo("true");
  }

  /** Created to reproduce issue 140 */
  @Test
  public void testStringFieldWithEmptyValueSerialization() {
    ClassWithEmptyStringFields target = new ClassWithEmptyStringFields();
    target.a = "5794749";
    String json = gson.toJson(target);
    assertThat(json).contains("\"a\":\"5794749\"");
    assertThat(json).contains("\"b\":\"\"");
    assertThat(json).contains("\"c\":\"\"");
  }

  /** Created to reproduce issue 140 */
  @Test
  public void testStringFieldWithEmptyValueDeserialization() {
    String json = "{a:\"5794749\",b:\"\",c:\"\"}";
    ClassWithEmptyStringFields target = gson.fromJson(json, ClassWithEmptyStringFields.class);
    assertThat(target.a).isEqualTo("5794749");
    assertThat(target.b).isEqualTo("");
    assertThat(target.c).isEqualTo("");
  }

  private static class ClassWithEmptyStringFields {
    String a = "";
    String b = "";
    String c = "";
  }

  @Test
  public void testJsonObjectSerialization() {
    Gson gson = new GsonBuilder().serializeNulls().create();
    JsonObject obj = new JsonObject();
    String json = gson.toJson(obj);
    assertThat(json).isEqualTo("{}");
  }

  /** Test for issue 215. */
  @Test
  public void testSingletonLists() {
    Gson gson = new Gson();
    Product product = new Product();
    assertThat(gson.toJson(product)).isEqualTo("{\"attributes\":[],\"departments\":[]}");
    Product deserialized = gson.fromJson(gson.toJson(product), Product.class);
    assertThat(deserialized.attributes).isEmpty();
    assertThat(deserialized.departments).isEmpty();

    product.departments.add(new Department());
    assertThat(gson.toJson(product))
        .isEqualTo("{\"attributes\":[],\"departments\":[{\"name\":\"abc\",\"code\":\"123\"}]}");
    deserialized = gson.fromJson(gson.toJson(product), Product.class);
    assertThat(deserialized.attributes).isEmpty();
    assertThat(deserialized.departments).hasSize(1);

    product.attributes.add("456");
    assertThat(gson.toJson(product))
        .isEqualTo(
            "{\"attributes\":[\"456\"],\"departments\":[{\"name\":\"abc\",\"code\":\"123\"}]}");
    deserialized = gson.fromJson(gson.toJson(product), Product.class);
    assertThat(deserialized.attributes).containsExactly("456");
    assertThat(deserialized.departments).hasSize(1);
  }

  static final class Department {
    public String name = "abc";
    public String code = "123";
  }

  static final class Product {
    private List<String> attributes = new ArrayList<>();
    private List<Department> departments = new ArrayList<>();
  }

  // http://code.google.com/p/google-gson/issues/detail?id=270
  @Test
  @SuppressWarnings("JavaUtilDate")
  public void testDateAsMapObjectField() {
    HasObjectMap a = new HasObjectMap();
    a.map.put("date", new Date(0));
    assertThat(gson.toJson(a))
        .matches("\\{\"map\":\\{\"date\":\"Dec 31, 1969,? 4:00:00\\hPM\"\\}\\}");
  }

  static class HasObjectMap {
    Map<String, Object> map = new HashMap<>();
  }

  /**
   * Tests serialization of a class with {@code static} field.
   *
   * <p>Important: It is not documented that this is officially supported; this test just checks the
   * current behavior.
   */
  @Test
  public void testStaticFieldSerialization() {
    // By default Gson should ignore static fields
    assertThat(gson.toJson(new ClassWithStaticField())).isEqualTo("{}");

    Gson gson =
        new GsonBuilder()
            // Include static fields
            .excludeFieldsWithModifiers(0)
            .create();

    String json = gson.toJson(new ClassWithStaticField());
    assertThat(json).isEqualTo("{\"s\":\"initial\"}");

    json = gson.toJson(new ClassWithStaticFinalField());
    assertThat(json).isEqualTo("{\"s\":\"initial\"}");
  }

  /**
   * Tests deserialization of a class with {@code static} field.
   *
   * <p>Important: It is not documented that this is officially supported; this test just checks the
   * current behavior.
   */
  @Test
  public void testStaticFieldDeserialization() {
    // By default Gson should ignore static fields
    ClassWithStaticField deserialized =
        gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticField.class);
    assertThat(deserialized).isNotNull();
    assertThat(ClassWithStaticField.s).isEqualTo("initial");

    Gson gson =
        new GsonBuilder()
            // Include static fields
            .excludeFieldsWithModifiers(0)
            .create();

    String oldValue = ClassWithStaticField.s;
    try {
      ClassWithStaticField obj = gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticField.class);
      assertThat(obj).isNotNull();
      assertThat(ClassWithStaticField.s).isEqualTo("custom");
    } finally {
      ClassWithStaticField.s = oldValue;
    }

    var e =
        assertThrows(
            JsonIOException.class,
            () -> gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticFinalField.class));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Cannot set value of 'static final' field"
                + " 'com.google.gson.functional.ObjectTest$ClassWithStaticFinalField#s'");
  }

  @SuppressWarnings({"PrivateConstructorForUtilityClass", "NonFinalStaticField"})
  static class ClassWithStaticField {
    static String s = "initial";
  }

  @SuppressWarnings("PrivateConstructorForUtilityClass")
  static class ClassWithStaticFinalField {
    static final String s = "initial";
  }

  @Test
  public void testThrowingDefaultConstructor() {
    // TODO: Adjust this once Gson throws more specific exception type
    var e =
        assertThrows(
            RuntimeException.class, () -> gson.fromJson("{}", ClassWithThrowingConstructor.class));
    assertThat(e)
        .hasMessageThat()
        .isEqualTo(
            "Failed to invoke constructor"
                + " 'com.google.gson.functional.ObjectTest$ClassWithThrowingConstructor()' with"
                + " no args");
    assertThat(e).hasCauseThat().isSameInstanceAs(ClassWithThrowingConstructor.thrownException);
  }

  static class ClassWithThrowingConstructor {
    @SuppressWarnings("StaticAssignmentOfThrowable")
    static final RuntimeException thrownException = new RuntimeException("Custom exception");

    public ClassWithThrowingConstructor() {
      throw thrownException;
    }
  }

  @Test
  public void testDeeplyNested() {
    int defaultLimit = 255;
    // json = {"r":{"r": ... {"r":null} ... }}
    String json = "{\"r\":".repeat(defaultLimit) + "null" + "}".repeat(defaultLimit);
    RecursiveClass deserialized = gson.fromJson(json, RecursiveClass.class);
    assertThat(deserialized).isNotNull();
    assertThat(deserialized.r).isNotNull();

    // json = {"r":{"r": ... {"r":null} ... }}
    String json2 = "{\"r\":".repeat(defaultLimit + 1) + "null" + "}".repeat(defaultLimit + 1);
    JsonSyntaxException e =
        assertThrows(JsonSyntaxException.class, () -> gson.fromJson(json2, RecursiveClass.class));
    assertThat(e).hasCauseThat().isInstanceOf(MalformedJsonException.class);
    assertThat(e)
        .hasCauseThat()
        .hasMessageThat()
        .isEqualTo(
            "Nesting limit 255 reached at line 1 column 1277 path $" + ".r".repeat(defaultLimit));
  }

  private static class RecursiveClass {
    RecursiveClass r;
  }
}