Gson Support for Java 8 Date-Time Types
Java 8 introduced a robust date and time API with classes such as LocalDate
, LocalDateTime
, and ZonedDateTime
. These classes provide an improved way to handle dates and times compared to the legacy java.util.Date
and java.util.Calendar
classes. However, by default, Gson does not support these new date-time types. This lack of support can lead to issues when trying to serialize and deserialize these types to and from JSON. This article will demonstrate how to create these custom serializers and deserializers for LocalDate
, LocalDateTime
, and ZonedDateTime
.
1. Set Up Gson
To use Gson, first, ensure the library is included in the project. If using Maven, add the following dependency to the pom.xml
:
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.11.0</version> </dependency>
2. Example Problem
Consider a simple Java class containing a LocalDate
field:
Person.java
public class Person { private String name; private LocalDate birthDate; public Person(String name, LocalDate birthDate) { this.name = name; this.birthDate = birthDate; } // Getters and setters omitted for brevity }
Now, let’s try to serialize an instance of this Person
class using default Gson:
DefaultGsonExample.java
public class DefaultGsonExample { public static void main(String[] args) { Person person = new Person("John Doe", LocalDate.of(2022, 10, 1)); Gson gson = new Gson(); String json = gson.toJson(person); System.out.println("Serialized JSON: " + json); } }
Output is:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.google.gson.internal.reflect.ReflectionHelper (file:/Users/omozegieaziegbe/.m2/repository/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar) to field java.time.LocalDate.year WARNING: Please consider reporting this to the maintainers of com.google.gson.internal.reflect.ReflectionHelper WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release Serialized JSON: {"name":"John Doe","birthDate":{"year":2022,"month":10,"day":1}}
As seen, the birthDate
field is not properly serialized. Now let’s try to deserialize a JSON string back to a Person
object:
DefaultGsonExample.java
public class DefaultGsonExample { public static void main(String[] args) { String json = "{\"name\":\"John Doe\",\"birthDate\":\"1990-01-01\"}"; Gson gson = new Gson(); Person person = gson.fromJson(json, Person.class); System.out.println("Deserialized Person: " + person.getBirthDate()); } }
Output is:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 33 path $.birthDate at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read (ReflectiveTypeAdapterFactory.java:397) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.readIntoField (ReflectiveTypeAdapterFactory.java:212) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter.readField (ReflectiveTypeAdapterFactory.java:433) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read (ReflectiveTypeAdapterFactory.java:393) at com.google.gson.Gson.fromJson (Gson.java:1227) at com.google.gson.Gson.fromJson (Gson.java:1137) at com.google.gson.Gson.fromJson (Gson.java:1047) at com.google.gson.Gson.fromJson (Gson.java:982) at com.jcg.defaultjsonexample.DefaultGsonExample.main (DefaultGsonExample.java:18) at org.codehaus.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:279) at java.lang.Thread.run (Thread.java:834)
The deserialization fails with a JsonSyntaxException
, indicating that Gson expected a JSON object but encountered a string instead. This occurs because Gson does not know how to handle the LocalDate
type, resulting in a mismatch between the expected and actual JSON structures.
3. Solution: Custom Serializers and Deserializers
To resolve this issue, we need to create custom serializers and deserializers for Java 8 date-time types and register them with Gson. These custom adapters convert LocalDateTime
instances to JSON strings and vice versa. This is crucial because LocalDateTime
is not natively supported by Gson, and attempting to serialize or deserialize LocalDateTime
objects without custom adapters will result in errors or incorrect data representation.
3.1 LocalDate Serializer and Deserializer
We will create a class for handling LocalDate
type in Gson.
LocalDateAdapter.java
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonSerializer; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> { private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; @Override public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.format(formatter)); } @Override public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return LocalDate.parse(json.getAsString(), formatter); } }
3.1.1 Register LocalDateAdapter with Gson
With the custom serializer and deserializer LocalDateAdapter
ready, we need to register it with a Gson
instance.
CustomDateTimeExample.java
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.time.LocalDate; public class CustomDateTimeExample { public static void main(String[] args) { Gson gson = new GsonBuilder() .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) .create(); Person person = new Person("John Doe", LocalDate.of(2022, 10, 1)); // Serialize String json = gson.toJson(person); System.out.println("Serialized Person with LocalDate: " + json); //Deserialize Person deserializedPerson = gson.fromJson(json, Person.class); System.out.println("Deserialized Person with LocalDate: " + deserializedPerson); } }
The program output is:
3.2 LocalDateTime Serializer and Deserializer
Create a LocalDateTimeAdapter
class to implement both JsonSerializer<LocalDateTime>
and JsonDeserializer<LocalDateTime>
. I used the DateTimeFormatter.ISO_LOCAL_DATE_TIME
to ensure that the date-time is formatted according to the ISO-8601 standard.
LocalDateTimeAdapter.java
import com.google.gson.JsonDeserializer; import com.google.gson.JsonSerializer; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import com.google.gson.JsonParseException; import java.lang.reflect.Type; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class LocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> { private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; @Override public JsonElement serialize(LocalDateTime src, Type typeOfSrc, com.google.gson.JsonSerializationContext context) { return new JsonPrimitive(src.format(formatter)); } @Override public LocalDateTime deserialize(JsonElement json, Type typeOfT, com.google.gson.JsonDeserializationContext context) throws JsonParseException { return LocalDateTime.parse(json.getAsString(), formatter); } }
Explanation
- Serialization: The
serialize
method takes aLocalDateTime
object and converts it to a JSON primitive string using the ISO-8601 format. This ensures that theLocalDateTime
is represented as a standard string in JSON. - Deserialization: The
deserialize
method takes a JSON element (expected to be a string) and converts it back to aLocalDateTime
object using the same ISO-8601 format. This ensures that the string is correctly parsed back into aLocalDateTime
instance.
3.2.1 Register LocalDateTimeAdapter with Gson
To handle LocalDateTime
objects correctly, we need to register our custom LocalDateTimeAdapter
with Gson. Here is how to register the LocalDateTimeAdapter
:
CustomDateTimeExample.java
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.time.LocalDateTime; public class CustomDateTimeExample { public static void main(String[] args) { Gson gson = new GsonBuilder() .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) .create(); Person person = new Person("John Doe", LocalDateTime.now()); // Serialize String json = gson.toJson(person); System.out.println("Serialized Person with LocalDateTime: " + json); //Deserialize Person deserializedPerson = gson.fromJson(json, Person.class); System.out.println("Deserialized Person with LocalDateTime: " + deserializedPerson); } }
Output is:
Serialized Person with LocalDateTime: {"name":"John Doe","birthDate":"2024-06-05T17:32:36.982656"} Deserialized Person with LocalDateTime: Person{name=John Doe, birthDate=2024-06-05T17:32:36.982656}
3.3 ZonedDateTime Serializer and Deserializer
Let’s create a ZonedDateTimeAdapter
class that implements both JsonSerializer<ZonedDateTime>
and JsonDeserializer<ZonedDateTime>
. I used the DateTimeFormatter.ISO_ZONED_DATE_TIME
to ensure that the date-time, including the time zone information, is formatted according to the ISO-8601 standard.
ZonedDateTimeAdapter.java
import com.google.gson.JsonDeserializer; import com.google.gson.JsonSerializer; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import com.google.gson.JsonParseException; import java.lang.reflect.Type; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class ZonedDateTimeAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> { private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME; @Override public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, com.google.gson.JsonSerializationContext context) { return new JsonPrimitive(src.format(formatter)); } @Override public ZonedDateTime deserialize(JsonElement json, Type typeOfT, com.google.gson.JsonDeserializationContext context) throws JsonParseException { return ZonedDateTime.parse(json.getAsString(), formatter); } }
3.3.1 Register ZonedDateTimeAdapter with Gson
Similarly, to handle ZonedDateTime
objects, we need to register our custom ZonedDateTimeAdapter
with Gson. This ensures that Gson knows how to correctly serialize and deserialize ZonedDateTime
instances, preserving both the date-time and the time zone information.
Here’s how to register the ZonedDateTimeAdapter
:
CustomDateTimeExample.java
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.time.ZonedDateTime; public class CustomDateTimeExample { public static void main(String[] args) { Gson gson = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) .create(); Person person = new Person("John Doe", ZonedDateTime.now()); // Serialize String json = gson.toJson(person); System.out.println("Serialized Person with ZonedDateTime: " + json); //Deserialize Person deserializedPerson = gson.fromJson(json, Person.class); System.out.println("Deserialized Person with ZonedDateTime: " + deserializedPerson); } }
In the code above, the registerTypeAdapter
method is used to register our custom-type adapter (ZonedDateTimeAdapter
) for the ZonedDateTime
class.
Output is:
Serialized Person with ZonedDateTime: {"name":"John Doe","birthDate":"2024-06-05T17:36:30.947555+01:00[Africa/Lagos]"} Deserialized Person with ZonedDateTime: Person{name=John Doe, birthDate=2024-06-05T17:36:30.947555+01:00[Africa/Lagos]}
5. Conclusion
In this article, we explored the challenges of using Gson to serialize and deserialize Java 8 date-time types such as LocalDate
, LocalDateTime
, and ZonedDateTime
. By default, Gson does not support these new date-time classes, leading to issues when attempting to convert them to and from JSON. We demonstrated the problems that arise with default Gson behaviour and provided a solution through custom serializers and deserializers.
We created custom adapters for each of the date-time types, ensuring that they are properly formatted according to the ISO-8601 standard. These adapters were then registered with a Gson
instance using GsonBuilder
, enabling seamless serialization and deserialization of LocalDate
, LocalDateTime
, and ZonedDateTime
.
By following these steps, you can extend Gson to fully support Java 8 date-time types, ensuring that your applications handle date and time data correctly and efficiently.
6. Download the Source Code
This was an article on how Gson supports Java 8 date time types.
You can download the full source code of this example here: Gson supports Java 8 date time types