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?
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.