Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 919
Kyle Gobel Avatar asked Sep 20 '25 13:09

Kyle Gobel


1 Answers

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.

like image 194
tgdavies Avatar answered Sep 22 '25 02:09

tgdavies