Skip to content

Commit b0da1e6

Browse files
authored
add handling of JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS (#277)
1 parent 818afc1 commit b0da1e6

13 files changed

+612
-26
lines changed

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.math.BigDecimal;
2121
import java.time.DateTimeException;
2222
import java.time.Duration;
23+
import java.util.Objects;
2324

2425
import com.fasterxml.jackson.annotation.JsonFormat;
2526

@@ -61,9 +62,17 @@ public class DurationDeserializer extends JSR310DeserializerBase<Duration>
6162
*/
6263
protected final DurationUnitConverter _durationUnitConverter;
6364

65+
/**
66+
* Flag for <code>JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS</code>
67+
*
68+
* @since 2.16
69+
*/
70+
protected final Boolean _readTimestampsAsNanosOverride;
71+
6472
public DurationDeserializer() {
6573
super(Duration.class);
6674
_durationUnitConverter = null;
75+
_readTimestampsAsNanosOverride = null;
6776
}
6877

6978
/**
@@ -72,6 +81,7 @@ public DurationDeserializer() {
7281
protected DurationDeserializer(DurationDeserializer base, Boolean leniency) {
7382
super(base, leniency);
7483
_durationUnitConverter = base._durationUnitConverter;
84+
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
7585
}
7686

7787
/**
@@ -80,6 +90,19 @@ protected DurationDeserializer(DurationDeserializer base, Boolean leniency) {
8090
protected DurationDeserializer(DurationDeserializer base, DurationUnitConverter converter) {
8191
super(base, base._isLenient);
8292
_durationUnitConverter = converter;
93+
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
94+
}
95+
96+
/**
97+
* @since 2.16
98+
*/
99+
protected DurationDeserializer(DurationDeserializer base,
100+
Boolean leniency,
101+
DurationUnitConverter converter,
102+
Boolean readTimestampsAsNanosOverride) {
103+
super(base, leniency);
104+
_durationUnitConverter = converter;
105+
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
83106
}
84107

85108
@Override
@@ -97,24 +120,31 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
97120
{
98121
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
99122
DurationDeserializer deser = this;
123+
boolean leniency = _isLenient;
124+
DurationUnitConverter unitConverter = _durationUnitConverter;
125+
Boolean timestampsAsNanosOverride = _readTimestampsAsNanosOverride;
100126
if (format != null) {
101127
if (format.hasLenient()) {
102-
Boolean leniency = format.getLenient();
103-
if (leniency != null) {
104-
deser = deser.withLeniency(leniency);
105-
}
128+
leniency = format.getLenient();
106129
}
107130
if (format.hasPattern()) {
108131
final String pattern = format.getPattern();
109-
DurationUnitConverter p = DurationUnitConverter.from(pattern);
110-
if (p == null) {
132+
unitConverter = DurationUnitConverter.from(pattern);
133+
if (unitConverter == null) {
111134
ctxt.reportBadDefinition(getValueType(ctxt),
112135
String.format(
113136
"Bad 'pattern' definition (\"%s\") for `Duration`: expected one of [%s]",
114137
pattern, DurationUnitConverter.descForAllowed()));
115138
}
116-
deser = deser.withConverter(p);
117139
}
140+
timestampsAsNanosOverride =
141+
format.getFeature(JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
142+
}
143+
if (leniency != _isLenient
144+
|| !Objects.equals(unitConverter, _durationUnitConverter)
145+
|| !Objects.equals(timestampsAsNanosOverride, _readTimestampsAsNanosOverride)) {
146+
return new DurationDeserializer(
147+
this, leniency, unitConverter, timestampsAsNanosOverride);
118148
}
119149
return deser;
120150
}
@@ -177,9 +207,14 @@ protected Duration _fromTimestamp(DeserializationContext ctxt, long ts) {
177207
}
178208
// 20-Oct-2020, tatu: This makes absolutely no sense but... somehow
179209
// became the default handling.
180-
if (ctxt.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
210+
if (shouldReadTimestampsAsNanoseconds(ctxt)) {
181211
return Duration.ofSeconds(ts);
182212
}
183213
return Duration.ofMillis(ts);
184214
}
215+
216+
protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
217+
return (_readTimestampsAsNanosOverride != null) ? _readTimestampsAsNanosOverride :
218+
context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
219+
}
185220
}

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ public class InstantDeserializer<T extends Temporal>
112112
*/
113113
protected final Boolean _adjustToContextTZOverride;
114114

115+
/**
116+
* Flag for <code>JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS</code>
117+
*
118+
* @since 2.16
119+
*/
120+
protected final Boolean _readTimestampsAsNanosOverride;
121+
115122
protected InstantDeserializer(Class<T> supportedType,
116123
DateTimeFormatter formatter,
117124
Function<TemporalAccessor, T> parsedToValue,
@@ -126,7 +133,8 @@ protected InstantDeserializer(Class<T> supportedType,
126133
this.fromNanoseconds = fromNanoseconds;
127134
this.adjust = adjust == null ? ((d, z) -> d) : adjust;
128135
this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ;
129-
_adjustToContextTZOverride = null;
136+
this._adjustToContextTZOverride = null;
137+
this._readTimestampsAsNanosOverride = null;
130138
}
131139

132140
@SuppressWarnings("unchecked")
@@ -139,6 +147,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f)
139147
adjust = base.adjust;
140148
replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
141149
_adjustToContextTZOverride = base._adjustToContextTZOverride;
150+
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
142151
}
143152

144153
@SuppressWarnings("unchecked")
@@ -151,6 +160,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToConte
151160
adjust = base.adjust;
152161
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
153162
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
163+
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
154164
}
155165

156166
@SuppressWarnings("unchecked")
@@ -163,19 +173,40 @@ protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f,
163173
adjust = base.adjust;
164174
replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
165175
_adjustToContextTZOverride = base._adjustToContextTZOverride;
176+
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
177+
}
178+
179+
/**
180+
* @since 2.16
181+
*/
182+
protected InstantDeserializer(InstantDeserializer<T> base,
183+
Boolean leniency,
184+
DateTimeFormatter formatter,
185+
JsonFormat.Shape shape,
186+
Boolean adjustToContextTimezoneOverride,
187+
Boolean readTimestampsAsNanosOverride)
188+
{
189+
super(base, leniency, formatter, shape);
190+
parsedToValue = base.parsedToValue;
191+
fromMilliseconds = base.fromMilliseconds;
192+
fromNanoseconds = base.fromNanoseconds;
193+
adjust = base.adjust;
194+
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
195+
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
196+
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
166197
}
167198

168199
@Override
169200
protected InstantDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
170201
if (dtf == _formatter) {
171202
return this;
172203
}
173-
return new InstantDeserializer<T>(this, dtf);
204+
return new InstantDeserializer<>(this, dtf);
174205
}
175206

176207
@Override
177208
protected InstantDeserializer<T> withLeniency(Boolean leniency) {
178-
return new InstantDeserializer<T>(this, _formatter, leniency);
209+
return new InstantDeserializer<>(this, _formatter, leniency);
179210
}
180211

181212
@Override
@@ -188,9 +219,14 @@ protected JSR310DateTimeDeserializerBase<?> _withFormatOverrides(Deserialization
188219
{
189220
InstantDeserializer<T> deser = (InstantDeserializer<T>) super._withFormatOverrides(ctxt,
190221
property, formatOverrides);
191-
Boolean B = formatOverrides.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
192-
if (!Objects.equals(B, deser._adjustToContextTZOverride)) {
193-
return new InstantDeserializer<T>(deser, B);
222+
Boolean adjustToContextTZOverride = formatOverrides.getFeature(
223+
JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
224+
Boolean readTimestampsAsNanosOverride = formatOverrides.getFeature(
225+
JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
226+
if (!Objects.equals(adjustToContextTZOverride, deser._adjustToContextTZOverride)
227+
|| !Objects.equals(readTimestampsAsNanosOverride, deser._readTimestampsAsNanosOverride)) {
228+
return new InstantDeserializer<>(deser, deser._isLenient, deser._formatter,
229+
deser._shape, adjustToContextTZOverride, readTimestampsAsNanosOverride);
194230
}
195231
return deser;
196232
}
@@ -230,6 +266,11 @@ protected boolean shouldAdjustToContextTimezone(DeserializationContext context)
230266
context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
231267
}
232268

269+
protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
270+
return (_readTimestampsAsNanosOverride != null) ? _readTimestampsAsNanosOverride :
271+
context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
272+
}
273+
233274
// Helper method to find Strings of form "all digits" and "digits-comma-digits"
234275
protected int _countPeriods(String str)
235276
{
@@ -305,7 +346,7 @@ protected T _fromString(JsonParser p, DeserializationContext ctxt,
305346

306347
protected T _fromLong(DeserializationContext context, long timestamp)
307348
{
308-
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
349+
if(shouldReadTimestampsAsNanoseconds(context)){
309350
return fromNanoseconds.apply(new FromDecimalArguments(
310351
timestamp, 0, this.getZone(context)
311352
));

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
8383
_shape = shape;
8484
}
8585

86+
/**
87+
* @since 2.16
88+
*/
89+
protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
90+
Boolean leniency,
91+
DateTimeFormatter formatter,
92+
JsonFormat.Shape shape) {
93+
super(base, leniency);
94+
_formatter = formatter;
95+
_shape = shape;
96+
}
97+
8698
protected abstract JSR310DateTimeDeserializerBase<T> withDateFormat(DateTimeFormatter dtf);
8799

88100
/**

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
import java.time.DateTimeException;
2121
import java.time.LocalDateTime;
2222
import java.time.format.DateTimeFormatter;
23+
import java.util.Objects;
2324

2425
import com.fasterxml.jackson.annotation.JsonFormat;
2526

2627
import com.fasterxml.jackson.core.JsonParser;
2728
import com.fasterxml.jackson.core.JsonToken;
2829
import com.fasterxml.jackson.core.JsonTokenId;
29-
30+
import com.fasterxml.jackson.databind.BeanProperty;
3031
import com.fasterxml.jackson.databind.DeserializationContext;
3132
import com.fasterxml.jackson.databind.DeserializationFeature;
3233
import com.fasterxml.jackson.databind.JavaType;
@@ -46,19 +47,40 @@ public class LocalDateTimeDeserializer
4647

4748
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
4849

50+
/**
51+
* Flag for <code>JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS</code>
52+
*
53+
* @since 2.16
54+
*/
55+
protected final Boolean _readTimestampsAsNanosOverride;
56+
4957
protected LocalDateTimeDeserializer() { // was private before 2.12
5058
this(DEFAULT_FORMATTER);
5159
}
5260

5361
public LocalDateTimeDeserializer(DateTimeFormatter formatter) {
5462
super(LocalDateTime.class, formatter);
63+
_readTimestampsAsNanosOverride = null;
5564
}
5665

5766
/**
5867
* Since 2.10
5968
*/
6069
protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base, Boolean leniency) {
6170
super(base, leniency);
71+
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
72+
}
73+
74+
/**
75+
* Since 2.16
76+
*/
77+
protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base,
78+
Boolean leniency,
79+
DateTimeFormatter formatter,
80+
JsonFormat.Shape shape,
81+
Boolean readTimestampsAsNanosOverride) {
82+
super(base, leniency, formatter, shape);
83+
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
6284
}
6385

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

99+
@Override
100+
protected JSR310DateTimeDeserializerBase<?> _withFormatOverrides(DeserializationContext ctxt,
101+
BeanProperty property, JsonFormat.Value formatOverrides) {
102+
LocalDateTimeDeserializer deser = (LocalDateTimeDeserializer)
103+
super._withFormatOverrides(ctxt, property, formatOverrides);
104+
Boolean readTimestampsAsNanosOverride = formatOverrides.getFeature(
105+
JsonFormat.Feature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
106+
if (!Objects.equals(readTimestampsAsNanosOverride, deser._readTimestampsAsNanosOverride)) {
107+
return new LocalDateTimeDeserializer(deser, deser._isLenient, deser._formatter,
108+
deser._shape, readTimestampsAsNanosOverride);
109+
}
110+
return deser;
111+
}
112+
77113
@Override
78114
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
79115
{
@@ -117,8 +153,7 @@ public LocalDateTime deserialize(JsonParser parser, DeserializationContext conte
117153
result = LocalDateTime.of(year, month, day, hour, minute, second);
118154
} else {
119155
int partialSecond = parser.getIntValue();
120-
if (partialSecond < 1_000 &&
121-
!context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS))
156+
if (partialSecond < 1_000 && !shouldReadTimestampsAsNanoseconds(context))
122157
partialSecond *= 1_000_000; // value is milliseconds, convert it to nanoseconds
123158
if (parser.nextToken() != JsonToken.END_ARRAY) {
124159
throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
@@ -142,6 +177,11 @@ public LocalDateTime deserialize(JsonParser parser, DeserializationContext conte
142177
return _handleUnexpectedToken(context, parser, "Expected array or string.");
143178
}
144179

180+
protected boolean shouldReadTimestampsAsNanoseconds(DeserializationContext context) {
181+
return (_readTimestampsAsNanosOverride != null) ? _readTimestampsAsNanosOverride :
182+
context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
183+
}
184+
145185
protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt,
146186
String string0) throws IOException
147187
{

0 commit comments

Comments
 (0)