Back to Repositories

Testing Java 17 Record Type Serialization in Google Gson

This test suite validates the Gson library’s handling of Java 17 Record types through reflective type adaptation. It focuses on serialization and deserialization of Record classes using UnixDomainPrincipal as a test case, ensuring proper JSON conversion for Java’s newer features.

Test Coverage Overview

The test suite provides comprehensive coverage of Record type handling in Gson, focusing on serialization and deserialization workflows.

Key areas tested include:
  • Custom adapter implementation for Record classes
  • Comparison with default reflection adapters
  • Serialization of complex Record objects with nested principals
  • Proper JSON structure preservation

Implementation Analysis

The testing approach utilizes JUnit framework with specialized type adapters for handling Principal objects. The implementation leverages reflection-based testing to verify Record class behavior without requiring Record support at the language level, using jdk.net.UnixDomainPrincipal as a test subject.

Technical patterns include:
  • Custom TypeAdapter implementation
  • Reflection-based instance creation
  • Comparison-based verification

Technical Details

Testing infrastructure includes:
  • JUnit test framework
  • Gson Builder configuration
  • Custom PrincipalTypeAdapter
  • Reflection utilities for Record handling
  • Truth assertion library for verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation using @Before setup
  • Clear test method naming conventions
  • Comprehensive assertion coverage
  • Type-safe adapter implementation
  • Effective use of reflection for testing platform-specific features

google/gson

gson/src/test/java/com/google/gson/internal/bind/Java17ReflectiveTypeAdapterFactoryTest.java

            
/*
 * Copyright (C) 2022 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.internal.bind;

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

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.reflect.Java17ReflectionHelperTest;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.UserPrincipal;
import java.security.Principal;
import org.junit.Before;
import org.junit.Test;

public class Java17ReflectiveTypeAdapterFactoryTest {

  // The class jdk.net.UnixDomainPrincipal is one of the few Record types that are included in the
  // JDK.
  // We use this to test serialization and deserialization of Record classes, so we do not need to
  // have record support at the language level for these tests. This class was added in JDK 16.
  Class<?> unixDomainPrincipalClass;

  @Before
  public void setUp() throws Exception {
    unixDomainPrincipalClass = Class.forName("jdk.net.UnixDomainPrincipal");
  }

  // Class for which the normal reflection based adapter is used
  private static class DummyClass {
    @SuppressWarnings("unused")
    public String s;
  }

  @Test
  public void testCustomAdapterForRecords() {
    Gson gson = new Gson();
    TypeAdapter<?> recordAdapter = gson.getAdapter(unixDomainPrincipalClass);
    TypeAdapter<?> defaultReflectionAdapter = gson.getAdapter(DummyClass.class);
    assertThat(defaultReflectionAdapter.getClass()).isNotEqualTo(recordAdapter.getClass());
  }

  @Test
  public void testSerializeRecords() throws ReflectiveOperationException {
    Gson gson =
        new GsonBuilder()
            .registerTypeAdapter(UserPrincipal.class, new PrincipalTypeAdapter<>())
            .registerTypeAdapter(GroupPrincipal.class, new PrincipalTypeAdapter<>())
            .create();

    UserPrincipal userPrincipal = gson.fromJson("\"user\"", UserPrincipal.class);
    GroupPrincipal groupPrincipal = gson.fromJson("\"group\"", GroupPrincipal.class);
    Object recordInstance =
        unixDomainPrincipalClass
            .getDeclaredConstructor(UserPrincipal.class, GroupPrincipal.class)
            .newInstance(userPrincipal, groupPrincipal);
    String serialized = gson.toJson(recordInstance);
    Object deserializedRecordInstance = gson.fromJson(serialized, unixDomainPrincipalClass);

    assertThat(deserializedRecordInstance).isEqualTo(recordInstance);
    assertThat(serialized).isEqualTo("{\"user\":\"user\",\"group\":\"group\"}");
  }

  private static class PrincipalTypeAdapter<T extends Principal> extends TypeAdapter<T> {
    @Override
    public void write(JsonWriter out, T principal) throws IOException {
      out.value(principal.getName());
    }

    @Override
    public T read(JsonReader in) throws IOException {
      String name = in.nextString();
      // This type adapter is only used for Group and User Principal, both of which are implemented
      // by PrincipalImpl.
      @SuppressWarnings("unchecked")
      T principal = (T) new Java17ReflectionHelperTest.PrincipalImpl(name);
      return principal;
    }
  }
}