Friday, June 7, 2024

Gson Support for Java 8 Date-Time Types

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:

Serialized Person with LocalDate: {"name":"John Doe","birthDate":"2022-10-01"}
Deserialized Person with LocalDate: Person(name=John Doe, birthDate=2022-10-01)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

Output from using custom serializer and deserializer for gson support with Java 8 local time

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 a LocalDateTime object and converts it to a JSON primitive string using the ISO-8601 format. This ensures that the LocalDateTime 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 a LocalDateTime object using the same ISO-8601 format. This ensures that the string is correctly parsed back into a LocalDateTime 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]}

Source: javacodegeeks.com

Related Posts

0 comments:

Post a Comment