Skip to content

Commit

Permalink
GsonBuilder: Explicitly set date format
Browse files Browse the repository at this point in the history
Between Java 17 and Java 21, serialization of DateTime has changed due to
CLDR 42 which uses a narrow non-breaking space.
To ease switching JDK versions, the seralization format is explicitly
set to the Java 17 format.

Signed-off-by: Holger Friedrich <[email protected]>
  • Loading branch information
holgerfriedrich committed Apr 20, 2024
1 parent 778159c commit ffb8692
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.openhab.core.auth.client.oauth2.OAuthException;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.library.types.DateTimeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -83,7 +84,8 @@ public OAuthConnector(HttpClientFactory httpClientFactory, GsonBuilder gsonBuild
public OAuthConnector(HttpClientFactory httpClientFactory, @Nullable Fields extraFields, GsonBuilder gsonBuilder) {
this.httpClientFactory = httpClientFactory;
this.extraFields = extraFields;
gson = gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
gson = gsonBuilder.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT)
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Instant.class, (JsonDeserializer<Instant>) (json, typeOfT, context) -> {
try {
return Instant.parse(json.getAsString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.StorageCipher;
import org.openhab.core.auth.oauth2client.internal.cipher.SymmetricKeyCipher;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.StorageService;
import org.osgi.service.component.annotations.Activate;
Expand Down Expand Up @@ -238,7 +239,7 @@ private class StorageFacade implements AutoCloseable {
public StorageFacade(Storage<String> storage) {
this.storage = storage;
// Add adapters for Instant
gson = new GsonBuilder()
gson = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT)
.registerTypeAdapter(Instant.class, (JsonDeserializer<Instant>) (json, typeOfT, context) -> {
try {
return Instant.parse(json.getAsString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.openhab.core.config.core.ConfigurationSerializer;
import org.openhab.core.config.core.OrderingMapSerializer;
import org.openhab.core.config.core.OrderingSetSerializer;
import org.openhab.core.library.types.DateTimeType;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Expand All @@ -44,6 +45,7 @@ public abstract class AbstractGSONParser<T> implements Parser<T> {

// A Gson instance to use by the parsers
protected static Gson gson = new GsonBuilder() //
.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT) //
.registerTypeAdapter(CompositeActionType.class, new ActionInstanceCreator()) //
.registerTypeAdapter(CompositeConditionType.class, new ConditionInstanceCreator()) //
.registerTypeAdapter(CompositeTriggerType.class, new TriggerInstanceCreator()) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javax.ws.rs.ext.MessageBodyWriter;

import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.library.types.DateTimeType;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
Expand Down Expand Up @@ -61,7 +62,7 @@ private static String mediaTypeWithoutParams(final MediaType mediaType) {
* Constructor.
*/
public MediaTypeExtension() {
final Gson gson = new GsonBuilder().create();
final Gson gson = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT).create();
readers.put(mediaTypeWithoutParams(MediaType.APPLICATION_JSON_TYPE), new GsonMessageBodyReader<>(gson));
readers.put(mediaTypeWithoutParams(MediaType.TEXT_PLAIN_TYPE), new PlainMessageBodyReader<>());
writers.put(mediaTypeWithoutParams(MediaType.APPLICATION_JSON_TYPE), new GsonMessageBodyWriter<>(gson));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.library.types.DateTimeType;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Expand All @@ -35,7 +36,7 @@
@NonNullByDefault
public class Stream2JSONInputStreamTest {

private static final Gson GSON = new GsonBuilder().create();
private static final Gson GSON = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT).create();

@Test
public void shouldReturnForEmptyStream() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.openhab.core.config.core.ConfigurationDeserializer;
import org.openhab.core.config.core.OrderingMapSerializer;
import org.openhab.core.config.core.OrderingSetSerializer;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.json.internal.migration.TypeMigrationException;
import org.openhab.core.storage.json.internal.migration.TypeMigrator;
Expand Down Expand Up @@ -104,12 +105,14 @@ public JsonStorage(File file, @Nullable ClassLoader classLoader, int maxBackupFi
this.typeMigrators = typeMigrators.stream().collect(Collectors.toMap(TypeMigrator::getOldType, e -> e));

this.internalMapper = new GsonBuilder() //
.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT) //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer())//
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer())//
.registerTypeHierarchyAdapter(Map.class, new StorageEntryMapDeserializer()) //
.setPrettyPrinting() //
.create();
this.entityMapper = new GsonBuilder() //
.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT) //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer())//
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer())//
.registerTypeAdapter(Configuration.class, new ConfigurationDeserializer()) //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
Expand All @@ -31,7 +35,10 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.test.java.JavaTest;

import com.google.gson.Gson;
Expand Down Expand Up @@ -171,7 +178,7 @@ public void testOrdering() throws IOException {
}
String storageStringReserialized = Files.readString(tmpFile.toPath());
assertEquals(storageStringAB, storageStringReserialized);
Gson gson = new GsonBuilder().create();
Gson gson = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT).create();

// Parse json. Gson preserves json object key ordering when we parse only JsonObject
JsonObject orderedMap = gson.fromJson(storageStringAB, JsonObject.class);
Expand Down Expand Up @@ -212,6 +219,46 @@ public void testOrdering() throws IOException {
.keySet().toArray());
}

@Test
@EnabledForJreRange(max = JRE.JAVA_19)
public void testDateSerialization17() {
// do NOT set format, setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT)!
Gson gson = new GsonBuilder().create();

// generate a Date instance for 1-Jan-1980 0:00, compensating local time zone
ZonedDateTime zdt = ZonedDateTime.of(1980, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault());
Date date = Date.from(Instant.ofEpochSecond(zdt.toEpochSecond()));
// \u20af will encode to 3 bytes, 0xe280af
assertEquals("\"Jan 1, 1980, 12:00:00 AM\"", gson.toJson(date));
}

// CLDR 42 introduced in Java 20 changes serialization of Date, adding a narrow non-breakable space
@Test
@EnabledForJreRange(min = JRE.JAVA_20)
public void testDateSerialization21() {
// do NOT set format, setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT)!
Gson gson = new GsonBuilder().create();

// generate a Date instance for 1-Jan-1980 0:00, compensating local time zone
ZonedDateTime zdt = ZonedDateTime.of(1980, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault());
Date date = Date.from(Instant.ofEpochSecond(zdt.toEpochSecond()));

// \u20af will encode to 3 bytes, 0xe280af
assertEquals("\"Jan 1, 1980, 12:00:00\u202fAM\"", gson.toJson(date));
}

// workaround, should work with Java 17 and 21
@Test
public void testDateSerialization() {
Gson gson = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT).create();

// generate a Date instance for 1-Jan-1980 0:00, compensating local time zone
ZonedDateTime zdt = ZonedDateTime.of(1980, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault());
Date date = Date.from(Instant.ofEpochSecond(zdt.toEpochSecond()));

assertEquals("\"Jan 1, 1980, 12:00:00 AM\"", gson.toJson(date));
}

private static class DummyObject {

// For the test here we use Linked variants of Map and Set which preserve the insertion order
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.core.config.core.OrderingMapSerializer;
import org.openhab.core.config.core.OrderingSetSerializer;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.storage.json.internal.migration.PersistedTransformationTypeMigrator;
import org.openhab.core.storage.json.internal.migration.TypeMigrationException;
import org.openhab.core.storage.json.internal.migration.TypeMigrator;
Expand All @@ -47,8 +48,9 @@
public class PersistedTransformationMigratorTest {

private final Gson internalMapper = new GsonBuilder() //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer())//
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer())//
.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT) //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer()) //
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer()) //
.registerTypeHierarchyAdapter(Map.class, new StorageEntryMapDeserializer()) //
.setPrettyPrinting() //
.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.core.config.core.OrderingMapSerializer;
import org.openhab.core.config.core.OrderingSetSerializer;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.storage.json.internal.migration.BridgeImplTypeMigrator;
import org.openhab.core.storage.json.internal.migration.ThingImplTypeMigrator;
import org.openhab.core.storage.json.internal.migration.TypeMigrationException;
Expand All @@ -49,8 +50,9 @@
public class ThingStorageEntityMigratorTest {

private final Gson internalMapper = new GsonBuilder() //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer())//
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer())//
.setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT) //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer()) //
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer()) //
.registerTypeHierarchyAdapter(Map.class, new StorageEntryMapDeserializer()) //
.setPrettyPrinting() //
.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class DateTimeType implements PrimitiveType, State, Command {
public static final String DATE_PATTERN_WITH_TZ_AND_MS = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public static final String DATE_PATTERN_WITH_TZ_AND_MS_GENERAL = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
public static final String DATE_PATTERN_WITH_TZ_AND_MS_ISO = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
// serialization of Date, Java 17 compatible format
public static final String DATE_PATTERN_JSON_COMPAT = "MMM d, yyyy, h:mm:ss aaa";

// internal patterns for parsing
private static final String DATE_PARSE_PATTERN_WITHOUT_TZ = "yyyy-MM-dd'T'HH:mm"
Expand Down

0 comments on commit ffb8692

Please sign in to comment.