GsonObjectSerializer.java
package com.reallifedeveloper.common.infrastructure;
import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.reallifedeveloper.common.application.notification.Notification;
import com.reallifedeveloper.common.domain.ErrorHandling;
import com.reallifedeveloper.common.domain.ObjectSerializer;
import com.reallifedeveloper.common.domain.event.DomainEvent;
/**
* An implementation of the {@link ObjectSerializer} that uses JSON as the serialized form.
*
* @author RealLifeDeveloper
*/
public class GsonObjectSerializer implements ObjectSerializer<String> {
/**
* The format used to parse and format date objects. The string is a pattern that can be used by a {@code java.time.DateTimeFormatter}.
*/
public static final String DATE_FORMAT = "yyyy-MM-dd";
/**
* The format used to parse and format {@code java.time.LocalDateTime} objects. The string is a pattern that can be
* used by a {@code DateTimeFormatter}.
*/
public static final String LOCAL_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
/**
* The format used to parse and format {@code java.time.ZonedDateTime} objects. The string is a pattern that can be
* used by a {@code DateTimeFormatter}.
*/
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_FORMAT);
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
private final Gson gson;
/**
* Creates a new {@code GsonObjectSerializer} with default values.
* <p>
* The default values include using the pattern {@value #DATE_TIME_FORMAT} when working with {@code java.time.ZonedDateTime} objects.
*/
public GsonObjectSerializer() {
gson = new GsonBuilder().setDateFormat(DATE_TIME_FORMAT)
.registerTypeAdapter(Notification.class, new NotificationDeserializer())
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
.registerTypeAdapter(LocalDateTime.class, new LocalDateTmeAdapter().nullSafe())
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTmeAdapter().nullSafe())
.create();
}
@Override
public String serialize(Object object) {
return gson.toJson(object);
}
@Override
public <U> U deserialize(String serializedObject, Class<U> objectType) {
if (serializedObject == null || objectType == null) {
throw new IllegalArgumentException(
"Arguments must not be null: serializedEvent=" + serializedObject + ", eventType=" + objectType);
}
try {
return gson.fromJson(serializedObject, objectType);
} catch (JsonSyntaxException e) {
throw new IllegalArgumentException("serializedEvent cannot be parsed as JSON: " + serializedObject, e);
}
}
private static final class NotificationDeserializer implements JsonDeserializer<Notification> {
@Override
public Notification deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
if (!Notification.class.getTypeName().equals(typeOfT.getTypeName())) {
throw new IllegalStateException("Unexpected type in deserialize method, expected 'Notification'. typeOfT=" + typeOfT);
}
try {
JsonObject jsonObject = json.getAsJsonObject();
String eventType = jsonObject.get("eventType").getAsString();
long storedEventId = jsonObject.get("storedEventId").getAsLong();
ZonedDateTime occurredOn = context.deserialize(jsonObject.get("occurredOn"), ZonedDateTime.class);
@SuppressWarnings("unchecked")
Class<DomainEvent> eventClass = (Class<DomainEvent>) Class.forName(eventType);
DomainEvent event = context.deserialize(jsonObject.get("event"), eventClass);
ErrorHandling.checkNull("JSON notification is missing event: json=" + json, event);
return new Notification(eventType, storedEventId, occurredOn, event);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Internal error: ", e);
}
}
}
private static final class LocalDateAdapter extends TypeAdapter<LocalDate> {
@Override
public void write(final JsonWriter jsonWriter, final LocalDate localDate) throws IOException {
jsonWriter.value(localDate.format(DATE_FORMATTER));
}
@Override
public LocalDate read(final JsonReader jsonReader) throws IOException {
return LocalDate.parse(jsonReader.nextString(), DATE_FORMATTER);
}
}
private static final class LocalDateTmeAdapter extends TypeAdapter<LocalDateTime> {
@Override
public void write(final JsonWriter jsonWriter, final LocalDateTime localDateTime) throws IOException {
jsonWriter.value(localDateTime.format(LOCAL_DATE_TIME_FORMATTER));
}
@Override
public LocalDateTime read(final JsonReader jsonReader) throws IOException {
return LocalDateTime.parse(jsonReader.nextString(), LOCAL_DATE_TIME_FORMATTER);
}
}
private static final class ZonedDateTmeAdapter extends TypeAdapter<ZonedDateTime> {
@Override
public void write(final JsonWriter jsonWriter, final ZonedDateTime zonedDateTime) throws IOException {
jsonWriter.value(zonedDateTime.format(DATE_TIME_FORMATTER));
}
@Override
public ZonedDateTime read(final JsonReader jsonReader) throws IOException {
return ZonedDateTime.parse(jsonReader.nextString(), DATE_TIME_FORMATTER);
}
}
}