Skip to content

add handling of JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS #277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.math.BigDecimal;
import java.time.DateTimeException;
import java.time.Duration;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonFormat;

Expand Down Expand Up @@ -61,9 +62,17 @@ public class DurationDeserializer extends JSR310DeserializerBase<Duration>
*/
protected final DurationUnitConverter _durationUnitConverter;

/**
* Flag for <code>JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS</code>
*
* @since 2.16
*/
protected final Boolean _readTimestampsAsNanosOverride;

public DurationDeserializer() {
super(Duration.class);
_durationUnitConverter = null;
_readTimestampsAsNanosOverride = null;
}

/**
Expand All @@ -72,6 +81,7 @@ public DurationDeserializer() {
protected DurationDeserializer(DurationDeserializer base, Boolean leniency) {
super(base, leniency);
_durationUnitConverter = base._durationUnitConverter;
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
}

/**
Expand All @@ -80,6 +90,19 @@ protected DurationDeserializer(DurationDeserializer base, Boolean leniency) {
protected DurationDeserializer(DurationDeserializer base, DurationUnitConverter converter) {
super(base, base._isLenient);
_durationUnitConverter = converter;
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
}

/**
* @since 2.16
*/
protected DurationDeserializer(DurationDeserializer base,
Boolean leniency,
DurationUnitConverter converter,
Boolean readTimestampsAsNanosOverride) {
super(base, leniency);
_durationUnitConverter = converter;
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
}

@Override
Expand All @@ -97,24 +120,31 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
{
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
DurationDeserializer deser = this;
boolean leniency = _isLenient;
DurationUnitConverter unitConverter = _durationUnitConverter;
Boolean timestampsAsNanosOverride = _readTimestampsAsNanosOverride;
if (format != null) {
if (format.hasLenient()) {
Boolean leniency = format.getLenient();
if (leniency != null) {
deser = deser.withLeniency(leniency);
}
leniency = format.getLenient();
}
if (format.hasPattern()) {
final String pattern = format.getPattern();
DurationUnitConverter p = DurationUnitConverter.from(pattern);
if (p == null) {
unitConverter = DurationUnitConverter.from(pattern);
if (unitConverter == null) {
ctxt.reportBadDefinition(getValueType(ctxt),
String.format(
"Bad 'pattern' definition (\"%s\") for `Duration`: expected one of [%s]",
pattern, DurationUnitConverter.descForAllowed()));
}
deser = deser.withConverter(p);
}
timestampsAsNanosOverride =
format.getFeature(JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
}
if (leniency != _isLenient
|| !Objects.equals(unitConverter, _durationUnitConverter)
|| !Objects.equals(timestampsAsNanosOverride, _readTimestampsAsNanosOverride)) {
return new DurationDeserializer(
this, leniency, unitConverter, timestampsAsNanosOverride);
}
return deser;
}
Expand Down Expand Up @@ -177,9 +207,14 @@ protected Duration _fromTimestamp(DeserializationContext ctxt, long ts) {
}
// 20-Oct-2020, tatu: This makes absolutely no sense but... somehow
// became the default handling.
if (ctxt.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
if (shouldReadTimestampsAsNanoseconds(ctxt)) {
return Duration.ofSeconds(ts);
}
return Duration.ofMillis(ts);
}

protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
return (_readTimestampsAsNanosOverride != null) ? _readTimestampsAsNanosOverride :
context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ public class InstantDeserializer<T extends Temporal>
*/
protected final Boolean _adjustToContextTZOverride;

/**
* Flag for <code>JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS</code>
*
* @since 2.16
*/
protected final Boolean _readTimestampsAsNanosOverride;

protected InstantDeserializer(Class<T> supportedType,
DateTimeFormatter formatter,
Function<TemporalAccessor, T> parsedToValue,
Expand All @@ -126,7 +133,8 @@ protected InstantDeserializer(Class<T> supportedType,
this.fromNanoseconds = fromNanoseconds;
this.adjust = adjust == null ? ((d, z) -> d) : adjust;
this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ;
_adjustToContextTZOverride = null;
this._adjustToContextTZOverride = null;
this._readTimestampsAsNanosOverride = null;
}

@SuppressWarnings("unchecked")
Expand All @@ -139,6 +147,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f)
adjust = base.adjust;
replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
_adjustToContextTZOverride = base._adjustToContextTZOverride;
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
}

@SuppressWarnings("unchecked")
Expand All @@ -151,6 +160,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToConte
adjust = base.adjust;
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
}

@SuppressWarnings("unchecked")
Expand All @@ -163,19 +173,40 @@ protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f,
adjust = base.adjust;
replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
_adjustToContextTZOverride = base._adjustToContextTZOverride;
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
}

/**
* @since 2.16
*/
protected InstantDeserializer(InstantDeserializer<T> base,
Boolean leniency,
DateTimeFormatter formatter,
JsonFormat.Shape shape,
Boolean adjustToContextTimezoneOverride,
Boolean readTimestampsAsNanosOverride)
{
super(base, leniency, formatter, shape);
parsedToValue = base.parsedToValue;
fromMilliseconds = base.fromMilliseconds;
fromNanoseconds = base.fromNanoseconds;
adjust = base.adjust;
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
}

@Override
protected InstantDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
if (dtf == _formatter) {
return this;
}
return new InstantDeserializer<T>(this, dtf);
return new InstantDeserializer<>(this, dtf);
}

@Override
protected InstantDeserializer<T> withLeniency(Boolean leniency) {
return new InstantDeserializer<T>(this, _formatter, leniency);
return new InstantDeserializer<>(this, _formatter, leniency);
}

@Override
Expand All @@ -188,9 +219,14 @@ protected JSR310DateTimeDeserializerBase<?> _withFormatOverrides(Deserialization
{
InstantDeserializer<T> deser = (InstantDeserializer<T>) super._withFormatOverrides(ctxt,
property, formatOverrides);
Boolean B = formatOverrides.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
if (!Objects.equals(B, deser._adjustToContextTZOverride)) {
return new InstantDeserializer<T>(deser, B);
Boolean adjustToContextTZOverride = formatOverrides.getFeature(
JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
Boolean readTimestampsAsNanosOverride = formatOverrides.getFeature(
JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
if (!Objects.equals(adjustToContextTZOverride, deser._adjustToContextTZOverride)
|| !Objects.equals(readTimestampsAsNanosOverride, deser._readTimestampsAsNanosOverride)) {
return new InstantDeserializer<>(deser, deser._isLenient, deser._formatter,
deser._shape, adjustToContextTZOverride, readTimestampsAsNanosOverride);
}
return deser;
}
Expand Down Expand Up @@ -230,6 +266,11 @@ protected boolean shouldAdjustToContextTimezone(DeserializationContext context)
context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
}

protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
return (_readTimestampsAsNanosOverride != null) ? _readTimestampsAsNanosOverride :
context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
}

// Helper method to find Strings of form "all digits" and "digits-comma-digits"
protected int _countPeriods(String str)
{
Expand Down Expand Up @@ -305,7 +346,7 @@ protected T _fromString(JsonParser p, DeserializationContext ctxt,

protected T _fromLong(DeserializationContext context, long timestamp)
{
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
if(shouldReadTimestampsAsNanoseconds(context)){
return fromNanoseconds.apply(new FromDecimalArguments(
timestamp, 0, this.getZone(context)
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
_shape = shape;
}

/**
* @since 2.16
*/
protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
Boolean leniency,
DateTimeFormatter formatter,
JsonFormat.Shape shape) {
super(base, leniency);
_formatter = formatter;
_shape = shape;
}

protected abstract JSR310DateTimeDeserializerBase<T> withDateFormat(DateTimeFormatter dtf);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonFormat;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;

import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
Expand All @@ -46,19 +47,40 @@ public class LocalDateTimeDeserializer

public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();

/**
* Flag for <code>JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS</code>
*
* @since 2.16
*/
protected final Boolean _readTimestampsAsNanosOverride;

protected LocalDateTimeDeserializer() { // was private before 2.12
this(DEFAULT_FORMATTER);
}

public LocalDateTimeDeserializer(DateTimeFormatter formatter) {
super(LocalDateTime.class, formatter);
_readTimestampsAsNanosOverride = null;
}

/**
* Since 2.10
*/
protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base, Boolean leniency) {
super(base, leniency);
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
}

/**
* Since 2.16
*/
protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base,
Boolean leniency,
DateTimeFormatter formatter,
JsonFormat.Shape shape,
Boolean readTimestampsAsNanosOverride) {
super(base, leniency, formatter, shape);
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
}

@Override
Expand All @@ -74,6 +96,20 @@ protected LocalDateTimeDeserializer withLeniency(Boolean leniency) {
@Override
protected LocalDateTimeDeserializer withShape(JsonFormat.Shape shape) { return this; }

@Override
protected JSR310DateTimeDeserializerBase<?> _withFormatOverrides(DeserializationContext ctxt,
BeanProperty property, JsonFormat.Value formatOverrides) {
LocalDateTimeDeserializer deser = (LocalDateTimeDeserializer)
super._withFormatOverrides(ctxt, property, formatOverrides);
Boolean readTimestampsAsNanosOverride = formatOverrides.getFeature(
JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
if (!Objects.equals(readTimestampsAsNanosOverride, deser._readTimestampsAsNanosOverride)) {
return new LocalDateTimeDeserializer(deser, deser._isLenient, deser._formatter,
deser._shape, readTimestampsAsNanosOverride);
}
return deser;
}

@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
Expand Down Expand Up @@ -117,8 +153,7 @@ public LocalDateTime deserialize(JsonParser parser, DeserializationContext conte
result = LocalDateTime.of(year, month, day, hour, minute, second);
} else {
int partialSecond = parser.getIntValue();
if (partialSecond < 1_000 &&
!context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS))
if (partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(context))
partialSecond *= 1_000_000; // value is milliseconds, convert it to nanoseconds
if (parser.nextToken() != JsonToken.END_ARRAY) {
throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
Expand All @@ -142,6 +177,11 @@ public LocalDateTime deserialize(JsonParser parser, DeserializationContext conte
return _handleUnexpectedToken(context, parser, "Expected array or string.");
}

protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
return (_readTimestampsAsNanosOverride != null) ? _readTimestampsAsNanosOverride :
context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
}

protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt,
String string0) throws IOException
{
Expand Down
Loading