Search code examples
javagsonjava-time

Why is gson looking for java.time.ZoneRegion when serializing java.time.ZoneId?


I'm trying to understand what Gson is doing here.

Here's my simple test case.

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.junit.Test;

import java.io.IOException;
import java.time.ZoneId;

public class TestSuite
{
   public class TestItem 
   {
      private ZoneId TimeZone = ZoneId.of("America/New_York");
   }

   class ZoneIdAdapter extends TypeAdapter<ZoneId>
    {
        @Override
        public void write(final JsonWriter jsonWriter, final ZoneId timeZone) throws IOException
        {
            jsonWriter.value(timeZone.getId());
        }

        @Override
        public ZoneId read(final JsonReader jsonReader) throws IOException
        {
            return ZoneId.of(jsonReader.nextString());
        }
    }
   
   @Test
   public void canSerializeTestItem()
   {
      Gson gson = new GsonBuilder()
          .registerTypeAdapter(ZoneId.class, new ZoneIdAdapter())
          .create();
      
      gson.toJson(new TestItem());
   }
}

For me using Java 17 and Gson 2.10 that fails with the following:

com.google.gson.JsonIOException: Failed making field 'java.time.ZoneRegion#id' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.

I'm failing to understand why gson is concerning itself with java.time.ZoneRegion#id when I thought* I already told it how to serialize a ZoneId (I'm guessing ZoneRegion is some internal to to java.time.ZoneId? I can't seem to find it.).

And what's more confusing to me, is that if I change to the following:

public class TestItem
{
   @Expose
   private ZoneId timeZone = ZoneId.of("America/New_York");
}

// ... omitted other stuff

 Gson gson = new GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .registerTypeAdapter(ZoneId.class, new ZoneIdAdapter())
    .create();

 gson.toJson(new TestItem());

It then works as I would expect it to (doesn't error).

Is there a better way to 'teach' gson how to serialize the third party types I'm concerned with?


Solution

  • The actual concrete type that ZoneId.of() returns is a ZoneRegion, and registerTypeAdapter only registers an adapter for a single type.

    As ZoneRegion is not a public class you can't (easily) register an adapter for it -- and of course you want a configuration which works even if you have an instance of TestItem where timeZone has a different concrete type.

    You can use registerTypeHierarchyAdapter instead. That registers an adapter for a class and all of its subclasses.

    I don't know why @Expose changes the behaviour.