#1684 - ENH: Support ISO date format for JSON marshalling on LocalDate, sql.Date etc

This commit is contained in:
rob bygrave
2019-04-26 19:00:49 +12:00
parent cf31830cbc
commit 28a70fa550
26 changed files with 330 additions and 156 deletions
@@ -5,6 +5,22 @@ package io.ebean.config;
*/
public abstract class JsonConfig {
/**
* Defined the format used for Date types.
*/
public enum Date {
/**
* Format as epoch millis.
*/
MILLIS,
/**
* Format as ISO-8601 date format.
*/
ISO8601
}
/**
* Defined the format used for DateTime types.
*/
@@ -171,6 +171,11 @@ public class ServerConfig {
*/
private JsonConfig.DateTime jsonDateTime = JsonConfig.DateTime.MILLIS;
/**
* The JSON format used for Date types. Default to millis.
*/
private JsonConfig.Date jsonDate = JsonConfig.Date.MILLIS;
/**
* For writing JSON specify if null values or empty collections should be exluded.
* By default all values are included.
@@ -683,6 +688,20 @@ public class ServerConfig {
this.jsonDateTime = jsonDateTime;
}
/**
* Return the JSON format used for Date types.
*/
public JsonConfig.Date getJsonDate() {
return jsonDate;
}
/**
* Set the JSON format to use for Date types.
*/
public void setJsonDate(JsonConfig.Date jsonDate) {
this.jsonDate = jsonDate;
}
/**
* Return the JSON include mode used when writing JSON.
*/
@@ -2988,12 +3007,8 @@ public class ServerConfig {
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude);
String jsonDateTimeFormat = p.get("jsonDateTime", null);
if (jsonDateTimeFormat != null) {
jsonDateTime = JsonConfig.DateTime.valueOf(jsonDateTimeFormat);
} else {
jsonDateTime = JsonConfig.DateTime.MILLIS;
}
jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime);
jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate);
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
ddlRun = p.getBoolean("ddl.run", ddlRun);
@@ -79,27 +79,24 @@ public class DefaultTypeFactory {
/**
* Create the default ScalarType for java.util.Date.
*/
public ScalarType<java.util.Date> createUtilDate(JsonConfig.DateTime mode) {
public ScalarType<java.util.Date> createUtilDate(JsonConfig.DateTime mode, JsonConfig.Date jsonDate) {
// by default map anonymous java.util.Date to java.sql.Timestamp.
// String mapType =
// properties.getProperty("type.mapping.java.util.Date","timestamp");
int utilDateType = getTemporalMapType("timestamp");
return createUtilDate(mode, utilDateType);
return createUtilDate(mode, jsonDate, utilDateType);
}
/**
* Create a ScalarType for java.util.Date explicitly specifying the type to
* map to.
*/
public ScalarType<java.util.Date> createUtilDate(JsonConfig.DateTime mode, int utilDateType) {
public ScalarType<java.util.Date> createUtilDate(JsonConfig.DateTime jsonDateTime, JsonConfig.Date jsonDate, int utilDateType) {
switch (utilDateType) {
case Types.DATE:
return new ScalarTypeUtilDate.DateType();
return new ScalarTypeUtilDate.DateType(jsonDate);
case Types.TIMESTAMP:
return new ScalarTypeUtilDate.TimestampType(mode);
return new ScalarTypeUtilDate.TimestampType(jsonDateTime);
default:
throw new RuntimeException("Invalid type " + utilDateType);
@@ -130,8 +130,6 @@ public final class DefaultTypeManager implements TypeManager {
private final ScalarType<?> timeType = new ScalarTypeTime();
private final ScalarType<?> dateType = new ScalarTypeDate();
private final ScalarType<?> urlType = new ScalarTypeURL();
private final ScalarType<?> uriType = new ScalarTypeURI();
private final ScalarType<?> localeType = new ScalarTypeLocale();
@@ -143,6 +141,7 @@ public final class DefaultTypeManager implements TypeManager {
private final ScalarType<?> classType = new ScalarTypeClass();
private final JsonConfig.DateTime jsonDateTime;
private final JsonConfig.Date jsonDate;
private final Object objectMapper;
@@ -189,6 +188,7 @@ public final class DefaultTypeManager implements TypeManager {
this.java7Present = config.getClassLoadConfig().isJava7Present();
this.jsonDateTime = config.getJsonDateTime();
this.jsonDate = config.getJsonDate();
this.typeMap = new ConcurrentHashMap<>();
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
@@ -205,9 +205,9 @@ public final class DefaultTypeManager implements TypeManager {
this.defaultEnumType = config.getDefaultEnumType();
initialiseStandard(jsonDateTime, config);
initialiseJavaTimeTypes(jsonDateTime, config);
initialiseJodaTypes(jsonDateTime, config);
initialiseStandard(config);
initialiseJavaTimeTypes(config);
initialiseJodaTypes(config);
initialiseJacksonTypes(config);
loadTypesFromProviders(config, objectMapper);
@@ -522,7 +522,7 @@ public final class DefaultTypeManager implements TypeManager {
}
// a util Date with jdbcType not matching server wide settings
if (type.equals(java.util.Date.class)) {
return extraTypeFactory.createUtilDate(jsonDateTime, jdbcType);
return extraTypeFactory.createUtilDate(jsonDateTime, jsonDate, jdbcType);
}
// a Calendar with jdbcType not matching server wide settings
if (type.equals(java.util.Calendar.class)) {
@@ -874,7 +874,7 @@ public final class DefaultTypeManager implements TypeManager {
}
}
private void initialiseJavaTimeTypes(JsonConfig.DateTime mode, ServerConfig config) {
private void initialiseJavaTimeTypes(ServerConfig config) {
if (java7Present) {
typeMap.put(java.nio.file.Path.class, new ScalarTypePath());
@@ -883,16 +883,16 @@ public final class DefaultTypeManager implements TypeManager {
if (config.getClassLoadConfig().isJavaTimePresent()) {
logger.debug("Registering java.time data types");
addType(java.time.Period.class, new ScalarTypePeriod());
addType(java.time.LocalDate.class, new ScalarTypeLocalDate());
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(mode));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(mode));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(mode));
addType(Instant.class, new ScalarTypeInstant(mode));
addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate));
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime));
addType(Instant.class, new ScalarTypeInstant(jsonDateTime));
addType(DayOfWeek.class, new ScalarTypeDayOfWeek());
addType(Month.class, new ScalarTypeMonth());
addType(Year.class, new ScalarTypeYear());
addType(YearMonth.class, new ScalarTypeYearMonthDate());
addType(YearMonth.class, new ScalarTypeYearMonthDate(jsonDate));
addType(MonthDay.class, new ScalarTypeMonthDay());
addType(OffsetTime.class, new ScalarTypeOffsetTime());
addType(ZoneId.class, new ScalarTypeZoneId());
@@ -915,16 +915,16 @@ public final class DefaultTypeManager implements TypeManager {
* Detect if Joda classes are in the classpath and if so register the Joda data types.
*/
@SuppressWarnings("deprecation")
private void initialiseJodaTypes(JsonConfig.DateTime mode, ServerConfig config) {
private void initialiseJodaTypes(ServerConfig config) {
// detect if Joda classes are in the classpath
if (config.getClassLoadConfig().isJodaTimePresent()) {
// Joda classes are in the classpath so register the types
logger.debug("Registering Joda data types");
addType(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(mode));
addType(DateTime.class, new ScalarTypeJodaDateTime(mode));
addType(LocalDate.class, new ScalarTypeJodaLocalDate());
addType(org.joda.time.DateMidnight.class, new ScalarTypeJodaDateMidnight());
addType(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(jsonDateTime));
addType(DateTime.class, new ScalarTypeJodaDateTime(jsonDateTime));
addType(LocalDate.class, new ScalarTypeJodaLocalDate(jsonDate));
addType(org.joda.time.DateMidnight.class, new ScalarTypeJodaDateMidnight(jsonDate));
addType(org.joda.time.Period.class, new ScalarTypeJodaPeriod());
String jodaLocalTimeMode = config.getJodaLocalTimeMode();
@@ -944,7 +944,7 @@ public final class DefaultTypeManager implements TypeManager {
* Register all the standard types supported. This is the standard JDBC types
* plus some other common types such as java.util.Date and java.util.Calendar.
*/
private void initialiseStandard(JsonConfig.DateTime mode, ServerConfig config) {
private void initialiseStandard(ServerConfig config) {
DatabasePlatform databasePlatform = config.getDatabasePlatform();
int platformClobType = databasePlatform.getClobDbType();
@@ -952,10 +952,10 @@ public final class DefaultTypeManager implements TypeManager {
nativeMap.put(DbPlatformType.HSTORE, hstoreType);
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(mode);
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(jsonDateTime, jsonDate);
addType(java.util.Date.class, utilDateType);
ScalarType<?> calType = extraTypeFactory.createCalendar(mode);
ScalarType<?> calType = extraTypeFactory.createCalendar(jsonDateTime);
addType(Calendar.class, calType);
ScalarType<?> mathBigIntType = extraTypeFactory.createMathBigInteger();
@@ -1079,10 +1079,12 @@ public final class DefaultTypeManager implements TypeManager {
// Temporal types
addType(Time.class, timeType);
nativeMap.put(Types.TIME, timeType);
ScalarTypeDate dateType = new ScalarTypeDate(jsonDate);
addType(Date.class, dateType);
nativeMap.put(Types.DATE, dateType);
ScalarType<?> timestampType = new ScalarTypeTimestamp(mode);
ScalarType<?> timestampType = new ScalarTypeTimestamp(jsonDateTime);
addType(Timestamp.class, timestampType);
nativeMap.put(Types.TIMESTAMP, timestampType);
}
@@ -1,9 +1,10 @@
package io.ebeaninternal.server.type;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.config.JsonConfig;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import java.io.DataInput;
import java.io.DataOutput;
@@ -17,8 +18,11 @@ import java.sql.Types;
*/
public abstract class ScalarTypeBaseDate<T> extends ScalarTypeBase<T> {
public ScalarTypeBaseDate(Class<T> type, boolean jdbcNative, int jdbcType) {
protected final JsonConfig.Date mode;
public ScalarTypeBaseDate(JsonConfig.Date mode, Class<T> type, boolean jdbcNative, int jdbcType) {
super(type, jdbcNative, jdbcType);
this.mode = mode;
}
/**
@@ -93,9 +97,20 @@ public abstract class ScalarTypeBaseDate<T> extends ScalarTypeBase<T> {
@Override
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
writer.writeNumber(convertToMillis(value));
switch(mode) {
case ISO8601:
writer.writeString(toIsoFormat(value));
break;
default:
writer.writeNumber(convertToMillis(value));
}
}
/**
* Convert the value to ISO8601 format.
*/
protected abstract String toIsoFormat(T value);
@Override
public DocPropertyType getDocType() {
return DocPropertyType.DATE;
@@ -19,7 +19,7 @@ import java.sql.Types;
public abstract class ScalarTypeBaseDateTime<T> extends ScalarTypeBase<T> {
protected final DateTimeJsonParser dateTimeParser = new DateTimeJsonParser();
protected final UtilDateTimeParser dateTimeParser = new UtilDateTimeParser();
protected final JsonConfig.DateTime mode;
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.type;
import io.ebean.config.JsonConfig;
import io.ebeaninternal.server.core.BasicTypeConverter;
import java.sql.Date;
@@ -11,8 +12,13 @@ import java.sql.Types;
*/
public class ScalarTypeDate extends ScalarTypeBaseDate<java.sql.Date> {
public ScalarTypeDate() {
super(Date.class, true, Types.DATE);
public ScalarTypeDate(JsonConfig.Date mode) {
super(mode, Date.class, true, Types.DATE);
}
@Override
protected String toIsoFormat(Date value) {
return value.toLocalDate().toString();
}
@Override
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.type;
import io.ebean.config.JsonConfig;
import io.ebeaninternal.server.core.BasicTypeConverter;
import org.joda.time.DateMidnight;
import java.sql.Date;
import java.sql.Types;
@@ -14,8 +16,13 @@ public class ScalarTypeJodaDateMidnight extends ScalarTypeBaseDate<org.joda.time
/**
* Instantiates a new scalar type joda date midnight.
*/
public ScalarTypeJodaDateMidnight() {
super(org.joda.time.DateMidnight.class, false, Types.DATE);
public ScalarTypeJodaDateMidnight(JsonConfig.Date mode) {
super(mode, org.joda.time.DateMidnight.class, false, Types.DATE);
}
@Override
protected String toIsoFormat(DateMidnight value) {
return value.toString();
}
@Override
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.type;
import io.ebean.config.JsonConfig;
import io.ebeaninternal.server.core.BasicTypeConverter;
import org.joda.time.LocalDate;
@@ -11,8 +12,13 @@ import java.sql.Types;
*/
public class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate<LocalDate> {
public ScalarTypeJodaLocalDate() {
super(LocalDate.class, false, Types.DATE);
public ScalarTypeJodaLocalDate(JsonConfig.Date mode) {
super(mode, LocalDate.class, false, Types.DATE);
}
@Override
protected String toIsoFormat(LocalDate value) {
return value.toString();
}
@Override
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.type;
import io.ebean.config.JsonConfig;
import io.ebeaninternal.server.core.BasicTypeConverter;
import java.sql.Date;
@@ -14,8 +15,12 @@ import java.time.ZonedDateTime;
*/
public class ScalarTypeLocalDate extends ScalarTypeBaseDate<LocalDate> {
public ScalarTypeLocalDate() {
super(LocalDate.class, false, Types.DATE);
public ScalarTypeLocalDate(JsonConfig.Date mode) {
super(mode, LocalDate.class, false, Types.DATE);
}
protected String toIsoFormat(LocalDate value) {
return value.toString();
}
@Override
@@ -55,5 +60,4 @@ public class ScalarTypeLocalDate extends ScalarTypeBaseDate<LocalDate> {
return new Timestamp(systemTimeMillis).toLocalDateTime().toLocalDate();
}
}
@@ -82,10 +82,13 @@ public class ScalarTypeUtilDate {
public static class DateType extends ScalarTypeBaseDate<java.util.Date> {
public DateType() {
super(Date.class, false, Types.DATE);
public DateType(JsonConfig.Date mode) {
super(mode, Date.class, false, Types.DATE);
}
protected String toIsoFormat(java.util.Date value) {
return UtilDateParser.format(value);
}
@Override
public long convertToMillis(java.util.Date value) {
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.type;
import io.ebean.config.JsonConfig;
import io.ebeaninternal.server.core.BasicTypeConverter;
import java.sql.Date;
@@ -15,8 +16,12 @@ import java.time.ZonedDateTime;
*/
public class ScalarTypeYearMonthDate extends ScalarTypeBaseDate<YearMonth> {
public ScalarTypeYearMonthDate() {
super(YearMonth.class, false, Types.DATE);
public ScalarTypeYearMonthDate(JsonConfig.Date mode) {
super(mode, YearMonth.class, false, Types.DATE);
}
protected String toIsoFormat(YearMonth value) {
return value.atDay(1).toString();
}
protected LocalDate toLocalDate(YearMonth yearMonth) {
@@ -0,0 +1,26 @@
package io.ebeaninternal.server.type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
class UtilDateParser {
private static final SimpleDateFormat dateTimeProto = new SimpleDateFormat("yyyy-MM-dd");
private static SimpleDateFormat formatter() {
return (SimpleDateFormat) dateTimeProto.clone();
}
static Date parse(String jsonDateTime) {
try {
return formatter().parse(jsonDateTime);
} catch (ParseException e) {
throw new RuntimeException("Error parsing Date[" + jsonDateTime + "]", e);
}
}
static String format(Date value) {
return formatter().format(value);
}
}
@@ -6,15 +6,14 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class DateTimeJsonParser {
class UtilDateTimeParser {
private final SimpleDateFormat dateTimeProto20;
private final SimpleDateFormat dateTimeProto22;
private final SimpleDateFormat dateTimeProto23;
private final SimpleDateFormat dateTimeProto24;
public DateTimeJsonParser() {
UtilDateTimeParser() {
dateTimeProto20 = init("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateTimeProto22 = init("yyyy-MM-dd'T'HH:mm:ss.S'Z'");
dateTimeProto23 = init("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");