GsonObjectSerializer.java

1
package com.reallifedeveloper.common.infrastructure;
2
3
import java.io.IOException;
4
import java.lang.reflect.Type;
5
import java.time.LocalDate;
6
import java.time.LocalDateTime;
7
import java.time.ZonedDateTime;
8
import java.time.format.DateTimeFormatter;
9
10
import com.google.gson.Gson;
11
import com.google.gson.GsonBuilder;
12
import com.google.gson.JsonDeserializationContext;
13
import com.google.gson.JsonDeserializer;
14
import com.google.gson.JsonElement;
15
import com.google.gson.JsonObject;
16
import com.google.gson.JsonSyntaxException;
17
import com.google.gson.TypeAdapter;
18
import com.google.gson.stream.JsonReader;
19
import com.google.gson.stream.JsonWriter;
20
21
import com.reallifedeveloper.common.application.notification.Notification;
22
import com.reallifedeveloper.common.domain.ErrorHandling;
23
import com.reallifedeveloper.common.domain.ObjectSerializer;
24
import com.reallifedeveloper.common.domain.event.DomainEvent;
25
26
/**
27
 * An implementation of the {@link ObjectSerializer} that uses JSON as the serialized form.
28
 *
29
 * @author RealLifeDeveloper
30
 */
31
public class GsonObjectSerializer implements ObjectSerializer<String> {
32
33
    /**
34
     * The format used to parse and format date objects. The string is a pattern that can be used by a {@code java.time.DateTimeFormatter}.
35
     */
36
    public static final String DATE_FORMAT = "yyyy-MM-dd";
37
38
    /**
39
     * The format used to parse and format {@code java.time.LocalDateTime} objects. The string is a pattern that can be used by a
40
     * {@code DateTimeFormatter}.
41
     */
42
    public static final String LOCAL_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
43
44
    /**
45
     * The format used to parse and format {@code java.time.ZonedDateTime} objects. The string is a pattern that can be used by a
46
     * {@code DateTimeFormatter}.
47
     */
48
    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
49
50
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
51
    private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_FORMAT);
52
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
53
54
    private final Gson gson;
55
56
    /**
57
     * Creates a new {@code GsonObjectSerializer} with default values.
58
     * <p>
59
     * The default values include using the pattern {@value #DATE_TIME_FORMAT} when working with {@code java.time.ZonedDateTime} objects.
60
     */
61
    public GsonObjectSerializer() {
62
        gson = new GsonBuilder().setDateFormat(DATE_TIME_FORMAT).registerTypeAdapter(Notification.class, new NotificationDeserializer())
63
                .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
64
                .registerTypeAdapter(LocalDateTime.class, new LocalDateTmeAdapter().nullSafe())
65
                .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTmeAdapter().nullSafe()).create();
66
    }
67
68
    @Override
69
    public String serialize(Object object) {
70 1 1. serialize : replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer::serialize → KILLED
        return gson.toJson(object);
71
    }
72
73
    @Override
74
    public <U> U deserialize(String serializedObject, Class<U> objectType) {
75 2 1. deserialize : negated conditional → KILLED
2. deserialize : negated conditional → KILLED
        if (serializedObject == null || objectType == null) {
76
            throw new IllegalArgumentException(
77
                    "Arguments must not be null: serializedObject=" + serializedObject + ", objectType=" + objectType);
78
        }
79
        try {
80 1 1. deserialize : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer::deserialize → KILLED
            return gson.fromJson(serializedObject, objectType);
81
        } catch (JsonSyntaxException e) {
82
            throw new IllegalArgumentException("serializedObject cannot be parsed as JSON: " + serializedObject, e);
83
        }
84
    }
85
86
    private static final class NotificationDeserializer implements JsonDeserializer<Notification> {
87
        @Override
88
        public Notification deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
89 1 1. deserialize : negated conditional → KILLED
            if (!Notification.class.getTypeName().equals(typeOfT.getTypeName())) {
90
                throw new IllegalStateException("Unexpected type in deserialize method, expected 'Notification'. typeOfT=" + typeOfT);
91
            }
92
            try {
93
                JsonObject jsonObject = json.getAsJsonObject();
94
                String eventType = jsonObject.get("eventType").getAsString();
95
                long storedEventId = jsonObject.get("storedEventId").getAsLong();
96
                ZonedDateTime occurredOn = context.deserialize(jsonObject.get("occurredOn"), ZonedDateTime.class);
97
                @SuppressWarnings("unchecked")
98
                Class<DomainEvent> eventClass = (Class<DomainEvent>) Class.forName(eventType);
99
                DomainEvent event = context.deserialize(jsonObject.get("event"), eventClass);
100 1 1. deserialize : removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → KILLED
                ErrorHandling.checkNull("JSON notification is missing event: json=" + json, event);
101 1 1. deserialize : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$NotificationDeserializer::deserialize → KILLED
                return new Notification(eventType, storedEventId, occurredOn, event);
102
            } catch (ClassNotFoundException e) {
103
                throw new IllegalStateException("Internal error: " + e.toString(), e);
104
            }
105
        }
106
    }
107
108
    private static final class LocalDateAdapter extends TypeAdapter<LocalDate> {
109
        @Override
110
        public void write(final JsonWriter jsonWriter, final LocalDate localDate) throws IOException {
111
            jsonWriter.value(localDate.format(DATE_FORMATTER));
112
        }
113
114
        @Override
115
        public LocalDate read(final JsonReader jsonReader) throws IOException {
116 1 1. read : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$LocalDateAdapter::read → KILLED
            return LocalDate.parse(jsonReader.nextString(), DATE_FORMATTER);
117
        }
118
    }
119
120
    private static final class LocalDateTmeAdapter extends TypeAdapter<LocalDateTime> {
121
        @Override
122
        public void write(final JsonWriter jsonWriter, final LocalDateTime localDateTime) throws IOException {
123
            jsonWriter.value(localDateTime.format(LOCAL_DATE_TIME_FORMATTER));
124
        }
125
126
        @Override
127
        public LocalDateTime read(final JsonReader jsonReader) throws IOException {
128 1 1. read : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$LocalDateTmeAdapter::read → KILLED
            return LocalDateTime.parse(jsonReader.nextString(), LOCAL_DATE_TIME_FORMATTER);
129
        }
130
    }
131
132
    private static final class ZonedDateTmeAdapter extends TypeAdapter<ZonedDateTime> {
133
        @Override
134
        public void write(final JsonWriter jsonWriter, final ZonedDateTime zonedDateTime) throws IOException {
135
            jsonWriter.value(zonedDateTime.format(DATE_TIME_FORMATTER));
136
        }
137
138
        @Override
139
        public ZonedDateTime read(final JsonReader jsonReader) throws IOException {
140 1 1. read : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$ZonedDateTmeAdapter::read → KILLED
            return ZonedDateTime.parse(jsonReader.nextString(), DATE_TIME_FORMATTER);
141
        }
142
    }
143
}

Mutations

70

1.1
Location : serialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:serializeNull()]
replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer::serialize → KILLED

75

1.1
Location : deserialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:deserializeNullEventType()]
negated conditional → KILLED

2.2
Location : deserialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:deserializeNullSerializedEvent()]
negated conditional → KILLED

80

1.1
Location : deserialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:serializeAndDeserializeNullLocalDateTime()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer::deserialize → KILLED

89

1.1
Location : deserialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:deserializeNotificationWithEmptyEvent()]
negated conditional → KILLED

100

1.1
Location : deserialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:deserializeNotificationWithEmptyEvent()]
removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → KILLED

101

1.1
Location : deserialize
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:serializeAndDeserializeNotification()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$NotificationDeserializer::deserialize → KILLED

116

1.1
Location : read
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:serializeAndDeserializeLocalDate()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$LocalDateAdapter::read → KILLED

128

1.1
Location : read
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:serializeAndDeserializeLocalDateTime()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$LocalDateTmeAdapter::read → KILLED

140

1.1
Location : read
Killed by : com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonObjectSerializerTest]/[method:serializeAndDeserializeEvent()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$ZonedDateTmeAdapter::read → KILLED

Active mutators

Tests examined


Report generated by PIT 1.20.0