Skip to content

Commit b10578c

Browse files
kupcicowtowncoder
authored andcommitted
issue 58 - NUMBER_INT should be specified when deserializing LocalDate as EpochDays (#142)
Fix #58 NUMBER_INT should be specified when deserializing LocalDate as EpochDays (unless lenient)
1 parent 5a0ad70 commit b10578c

File tree

10 files changed

+140
-5
lines changed

10 files changed

+140
-5
lines changed

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ protected InstantDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
164164
protected InstantDeserializer<T> withLeniency(Boolean leniency) {
165165
return this;
166166
}
167-
167+
168+
@Override
169+
protected InstantDeserializer<T> withShape(JsonFormat.Shape shape) { return this; }
170+
168171
@SuppressWarnings("unchecked")
169172
@Override
170173
public T deserialize(JsonParser parser, DeserializationContext context) throws IOException

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

+41
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.fasterxml.jackson.annotation.JsonFormat;
99
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
10+
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
1011
import com.fasterxml.jackson.core.JsonParser;
1112
import com.fasterxml.jackson.core.JsonToken;
1213
import com.fasterxml.jackson.databind.BeanProperty;
@@ -39,10 +40,24 @@ public abstract class JSR310DateTimeDeserializerBase<T>
3940
*/
4041
protected final boolean _isLenient;
4142

43+
/**
44+
* Setting that indicates the {@Link JsonFormat.Shape} specified for this deserializer
45+
* as a {@link JsonFormat.Shape} annotation on property or class, or due to per-type
46+
* "config override", or from global settings:
47+
* If Shape is NUMBER_INT, the input value is considered to be epoch days. If not a
48+
* NUMBER_INT, and the deserializer was not specified with the leniency setting of true,
49+
* then an exception will be thrown.
50+
* @see [jackson-modules-java8#58] for more info
51+
*
52+
* @since 2.11
53+
*/
54+
protected final Shape _shape;
55+
4256
protected JSR310DateTimeDeserializerBase(Class<T> supportedType, DateTimeFormatter f) {
4357
super(supportedType);
4458
_formatter = f;
4559
_isLenient = true;
60+
_shape = null;
4661
}
4762

4863
/**
@@ -53,6 +68,7 @@ protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
5368
super(base);
5469
_formatter = f;
5570
_isLenient = base._isLenient;
71+
_shape = base._shape;
5672
}
5773

5874
/**
@@ -63,15 +79,34 @@ protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
6379
super(base);
6480
_formatter = base._formatter;
6581
_isLenient = !Boolean.FALSE.equals(leniency);
82+
_shape = base._shape;
83+
}
84+
85+
/**
86+
* @since 2.11
87+
*/
88+
protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
89+
Shape shape) {
90+
super(base);
91+
_formatter = base._formatter;
92+
_shape = shape;
93+
_isLenient = base._isLenient;
6694
}
6795

96+
6897
protected abstract JSR310DateTimeDeserializerBase<T> withDateFormat(DateTimeFormatter dtf);
6998

7099
/**
71100
* @since 2.10
72101
*/
73102
protected abstract JSR310DateTimeDeserializerBase<T> withLeniency(Boolean leniency);
74103

104+
/**
105+
* @since 2.11
106+
*/
107+
protected abstract JSR310DateTimeDeserializerBase<T> withShape(Shape shape);
108+
109+
75110
@Override
76111
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
77112
BeanProperty property) throws JsonMappingException
@@ -107,6 +142,12 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
107142
deser = deser.withLeniency(leniency);
108143
}
109144
}
145+
//Issue #58: For LocalDate deserializers we need to configure the formatter with
146+
//a shape picked up from JsonFormat annotation, to decide if the value is EpochSeconds
147+
JsonFormat.Shape shape = format.getShape();
148+
if (shape != null && shape != _shape) {
149+
deser = deser.withShape(shape);
150+
}
110151
// any use for TimeZone?
111152
}
112153
return deser;

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

+15-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.time.ZoneOffset;
2525
import java.time.format.DateTimeFormatter;
2626

27+
import com.fasterxml.jackson.annotation.JsonFormat;
2728
import com.fasterxml.jackson.core.*;
2829
import com.fasterxml.jackson.databind.DeserializationContext;
2930
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -63,6 +64,13 @@ protected LocalDateDeserializer(LocalDateDeserializer base, Boolean leniency) {
6364
super(base, leniency);
6465
}
6566

67+
/**
68+
* Since 2.11
69+
*/
70+
protected LocalDateDeserializer(LocalDateDeserializer base, JsonFormat.Shape shape) {
71+
super(base, shape);
72+
}
73+
6674
@Override
6775
protected LocalDateDeserializer withDateFormat(DateTimeFormatter dtf) {
6876
return new LocalDateDeserializer(this, dtf);
@@ -73,6 +81,9 @@ protected LocalDateDeserializer withLeniency(Boolean leniency) {
7381
return new LocalDateDeserializer(this, leniency);
7482
}
7583

84+
@Override
85+
protected LocalDateDeserializer withShape(JsonFormat.Shape shape) { return new LocalDateDeserializer(this, shape); }
86+
7687
@Override
7788
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException
7889
{
@@ -136,10 +147,11 @@ public LocalDate deserialize(JsonParser parser, DeserializationContext context)
136147
}
137148
// 06-Jan-2018, tatu: Is this actually safe? Do users expect such coercion?
138149
if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
139-
if (!isLenient()) {
140-
return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
150+
// issue 58 - also check for NUMBER_INT, which needs to be specified when serializing.
151+
if (_shape == JsonFormat.Shape.NUMBER_INT || isLenient()) {
152+
return LocalDate.ofEpochDay(parser.getLongValue());
141153
}
142-
return LocalDate.ofEpochDay(parser.getLongValue());
154+
return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
143155
}
144156
return _handleUnexpectedToken(context, parser, "Expected array or string.");
145157
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.time.ZoneOffset;
2424
import java.time.format.DateTimeFormatter;
2525

26+
import com.fasterxml.jackson.annotation.JsonFormat;
2627
import com.fasterxml.jackson.core.JsonParser;
2728
import com.fasterxml.jackson.core.JsonToken;
2829
import com.fasterxml.jackson.core.JsonTokenId;
@@ -69,6 +70,9 @@ protected LocalDateTimeDeserializer withLeniency(Boolean leniency) {
6970
return new LocalDateTimeDeserializer(this, leniency);
7071
}
7172

73+
@Override
74+
protected LocalDateTimeDeserializer withShape(JsonFormat.Shape shape) { return this; }
75+
7276
@Override
7377
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
7478
{

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

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.time.LocalTime;
2222
import java.time.format.DateTimeFormatter;
2323

24+
import com.fasterxml.jackson.annotation.JsonFormat;
2425
import com.fasterxml.jackson.core.JsonParser;
2526
import com.fasterxml.jackson.core.JsonToken;
2627
import com.fasterxml.jackson.databind.DeserializationContext;
@@ -58,6 +59,9 @@ protected LocalTimeDeserializer withLeniency(Boolean leniency) {
5859
return this;
5960
}
6061

62+
@Override
63+
protected LocalTimeDeserializer withShape(JsonFormat.Shape shape) { return this; }
64+
6165
@Override
6266
public LocalTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
6367
{

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

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.time.MonthDay;
66
import java.time.format.DateTimeFormatter;
77

8+
import com.fasterxml.jackson.annotation.JsonFormat;
89
import com.fasterxml.jackson.core.JsonParser;
910
import com.fasterxml.jackson.core.JsonToken;
1011
import com.fasterxml.jackson.databind.DeserializationContext;
@@ -34,6 +35,9 @@ protected MonthDayDeserializer withLeniency(Boolean leniency) {
3435
return this;
3536
}
3637

38+
@Override
39+
protected MonthDayDeserializer withShape(JsonFormat.Shape shape) { return this; }
40+
3741
@Override
3842
public MonthDay deserialize(JsonParser parser, DeserializationContext context) throws IOException
3943
{

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

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.time.ZoneOffset;
2323
import java.time.format.DateTimeFormatter;
2424

25+
import com.fasterxml.jackson.annotation.JsonFormat;
2526
import com.fasterxml.jackson.core.*;
2627
import com.fasterxml.jackson.databind.*;
2728

@@ -55,6 +56,9 @@ protected OffsetTimeDeserializer withLeniency(Boolean leniency) {
5556
return this;
5657
}
5758

59+
@Override
60+
protected OffsetTimeDeserializer withShape(JsonFormat.Shape shape) { return this; }
61+
5862
@Override
5963
public OffsetTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
6064
{

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

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.fasterxml.jackson.datatype.jsr310.deser;
1818

19+
import com.fasterxml.jackson.annotation.JsonFormat;
1920
import com.fasterxml.jackson.core.JsonParser;
2021
import com.fasterxml.jackson.core.JsonToken;
2122
import com.fasterxml.jackson.databind.DeserializationContext;
@@ -56,6 +57,9 @@ protected YearDeserializer withLeniency(Boolean leniency) {
5657
return this;
5758
}
5859

60+
@Override
61+
protected YearDeserializer withShape(JsonFormat.Shape shape) { return this; }
62+
5963
@Override
6064
public Year deserialize(JsonParser parser, DeserializationContext context) throws IOException
6165
{

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

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.time.YearMonth;
2222
import java.time.format.DateTimeFormatter;
2323

24+
import com.fasterxml.jackson.annotation.JsonFormat;
2425
import com.fasterxml.jackson.core.JsonParser;
2526
import com.fasterxml.jackson.databind.DeserializationContext;
2627
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -59,6 +60,9 @@ protected YearMonthDeserializer withLeniency(Boolean leniency) {
5960
return this;
6061
}
6162

63+
@Override
64+
protected YearMonthDeserializer withShape(JsonFormat.Shape shape) { return this; }
65+
6266
@Override
6367
public YearMonth deserialize(JsonParser parser, DeserializationContext context) throws IOException
6468
{

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateDeserTest.java

+56-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,15 @@ final static class Wrapper {
4545
public Wrapper() { }
4646
public Wrapper(LocalDate v) { value = v; }
4747
}
48-
48+
49+
final static class ShapeWrapper {
50+
@JsonFormat(shape=JsonFormat.Shape.NUMBER_INT)
51+
public LocalDate date;
52+
53+
public ShapeWrapper() { }
54+
public ShapeWrapper(LocalDate v) { date = v; }
55+
}
56+
4957
/*
5058
/**********************************************************
5159
/* Deserialization from Int array representation
@@ -341,6 +349,53 @@ public void testDeserializationCaseInsensitiveDisabled_InvalidDate() throws Thro
341349
}
342350
}
343351

352+
/*
353+
/**********************************************************************
354+
/*
355+
* Tests for issue 58 - NUMBER_INT should be specified when deserializing
356+
* LocalDate as EpochDays
357+
*/
358+
/**********************************************************************
359+
*/
360+
@Test
361+
public void testLenientDeserializeFromNumberInt() throws Exception {
362+
ObjectMapper mapper = newMapper();
363+
mapper.configOverride(LocalDate.class)
364+
.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.NUMBER_INT));
365+
366+
assertEquals("The value is not correct.", LocalDate.of(1970, Month.MAY, 04),
367+
mapper.readValue("123", LocalDate.class));
368+
}
369+
370+
@Test
371+
public void testStrictDeserializeFromNumberInt() throws Exception
372+
{
373+
ObjectMapper mapper = newMapper();
374+
mapper.configOverride(LocalDate.class)
375+
.setFormat(JsonFormat.Value.forLeniency(false));
376+
377+
ShapeWrapper w = mapper.readValue("{\"date\":123}", ShapeWrapper.class);
378+
LocalDate localDate = w.date;
379+
380+
assertEquals("The value is not correct.", LocalDate.of(1970, Month.MAY, 04),
381+
localDate);
382+
}
383+
384+
@Test(expected = MismatchedInputException.class)
385+
public void testStrictDeserializeFromString() throws Exception
386+
{
387+
ObjectMapper mapper = newMapper();
388+
mapper.configOverride(LocalDate.class)
389+
.setFormat(JsonFormat.Value.forLeniency(false));
390+
391+
mapper.readValue("{\"value\":123}", Wrapper.class);
392+
}
393+
394+
/*
395+
/**********************************************************************
396+
/* Helper methods
397+
/**********************************************************************
398+
*/
344399
private void expectFailure(ObjectReader reader, String json) throws Throwable {
345400
try {
346401
reader.readValue(aposToQuotes(json));

0 commit comments

Comments
 (0)