Skip to content
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

Solve #80 (jackson-modules-java8): Support case-insensitive date/time #83

Merged
merged 4 commits into from
Sep 22, 2019
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
1 change: 0 additions & 1 deletion datetime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@
<Xmaxerrs>10000</Xmaxerrs>
<Xmaxwarns>10000</Xmaxwarns>
<Xlint />
<Werror />
</compilerArguments>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Locale;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.util.ClassUtil;

Expand Down Expand Up @@ -79,11 +82,16 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
if (format.hasPattern()) {
final String pattern = format.getPattern();
final Locale locale = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
if (acceptCaseInsensitiveValues(ctxt, format)) {
builder.parseCaseInsensitive();
}
builder.appendPattern(pattern);
DateTimeFormatter df;
if (locale == null) {
df = DateTimeFormatter.ofPattern(pattern);
df = builder.toFormatter();
} else {
df = DateTimeFormatter.ofPattern(pattern, locale);
df = builder.toFormatter(locale);
}
//Issue #69: For instant serializers/deserializers we need to configure the formatter with
//a time zone picked up from JsonFormat annotation, otherwise serialization might not work
Expand Down Expand Up @@ -112,6 +120,15 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
protected boolean isLenient() {
return _isLenient;
}

private boolean acceptCaseInsensitiveValues(DeserializationContext ctxt, JsonFormat.Value format)
{
Boolean enabled = format.getFeature( Feature.ACCEPT_CASE_INSENSITIVE_VALUES);
if( enabled == null) {
enabled = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES);
}
return enabled;
}

protected void _throwNoNumericTimestampNeedTimeZone(JsonParser p, DeserializationContext ctxt)
throws IOException
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
package com.fasterxml.jackson.datatype.jsr310.deser;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.time.temporal.Temporal;
import java.util.Map;

import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
import com.fasterxml.jackson.annotation.JsonFormat.Value;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
Expand Down Expand Up @@ -221,7 +227,7 @@ public void testStrictDeserializFromEmptyString() throws Exception {
public void testDeserializationAsArrayDisabled() throws Throwable
{
try {
READER.readValue("[\"2000-01-01\"]");
read(READER, "[\"2000-01-01\"]");
fail("expected MismatchedInputException");
} catch (MismatchedInputException e) {
verifyException(e, "Unexpected token (VALUE_STRING) within Array");
Expand All @@ -232,9 +238,57 @@ public void testDeserializationAsArrayDisabled() throws Throwable
public void testDeserializationAsEmptyArrayDisabled() throws Throwable
{
// works even without the feature enabled
assertNull(READER.readValue("[]"));
assertNull(read(READER, "[]"));
}

@Test
public void testDeserializationCaseInsensitiveEnabledOnValue() throws Throwable
{
ObjectMapper mapper = newMapper();
Value format = JsonFormat.Value
.forPattern("dd-MMM-yyyy")
.withFeature(Feature.ACCEPT_CASE_INSENSITIVE_VALUES);
mapper.configOverride(LocalDate.class).setFormat(format);
ObjectReader reader = mapper.readerFor(LocalDate.class);
String[] jsons = new String[] {"'01-Jan-2000'","'01-JAN-2000'", "'01-jan-2000'"};
for(String json : jsons) {
expectSuccess(reader, LocalDate.of(2000, Month.JANUARY, 1), json);
}
}

@Test
public void testDeserializationCaseInsensitiveEnabled() throws Throwable
{
ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, true);
mapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy"));
ObjectReader reader = mapper.readerFor(LocalDate.class);
String[] jsons = new String[] {"'01-Jan-2000'","'01-JAN-2000'", "'01-jan-2000'"};
for(String json : jsons) {
expectSuccess(reader, LocalDate.of(2000, Month.JANUARY, 1), json);
}
}

@Test
public void testDeserializationCaseInsensitiveDisabled() throws Throwable
{
ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false);
mapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy"));
ObjectReader reader = mapper.readerFor(LocalDate.class);
expectSuccess(reader, LocalDate.of(2000, Month.JANUARY, 1), "'01-Jan-2000'");
}

@Test
public void testDeserializationCaseInsensitiveDisabled_InvalidDate() throws Throwable
{
ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false);
mapper.configOverride(LocalDate.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy"));
ObjectReader reader = mapper.readerFor(LocalDate.class);
String[] jsons = new String[] {"'01-JAN-2000'", "'01-jan-2000'"};
for(String json : jsons) {
expectFailure(reader, json);
}
}

@Test
public void testDeserializationAsArrayEnabled() throws Throwable
{
Expand All @@ -253,12 +307,40 @@ public void testDeserializationAsEmptyArrayEnabled() throws Throwable
.readValue("[]");
assertNull(value);
}

private void expectFailure(ObjectReader reader, String json) throws Throwable {
try {
read(reader, json);
fail("expected DateTimeParseException");
} catch (JsonProcessingException e) {
if (e.getCause() == null) {
throw e;
}
if (!(e.getCause() instanceof DateTimeParseException)) {
throw e.getCause();
}
} catch (IOException e) {
throw e;
}
}

/*
/**********************************************************
/* Polymorphic deser
/**********************************************************
*/
private void expectSuccess(ObjectReader reader, Object exp, String json) throws IOException {
final LocalDate value = read(reader, json);
notNull(value);
expect(exp, value);
}

private LocalDate read(ObjectReader reader, final String json) throws IOException {
return reader.readValue(aposToQuotes(json));
}

private static void notNull(Object value) {
assertNotNull("The value should not be null.", value);
}

private static void expect(Object exp, Object value) {
assertEquals("The value is not correct.", exp, value);
}

@Test
public void testDeserializationWithTypeInfo01() throws Exception
Expand All @@ -285,7 +367,9 @@ public void testCustomFormat() throws Exception
{
Wrapper w = MAPPER.readValue("{\"value\":\"2015_07_28T13:53+0300\"}", Wrapper.class);
LocalDate date = w.value;
assertNotNull(date);
assertEquals(28, date.getDayOfMonth());
}
}



Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,33 @@

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

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.time.temporal.Temporal;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
import com.fasterxml.jackson.annotation.JsonFormat.Value;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;

import com.fasterxml.jackson.datatype.jsr310.MockObjectConfiguration;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;

import org.junit.Test;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.time.temporal.Temporal;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;

import static org.junit.Assert.*;

public class LocalDateTimeDeserTest
Expand Down Expand Up @@ -401,4 +402,87 @@ public void testDeserilizeFromSimpleTimestamp() throws Exception
verifyException(e, "raw timestamp (1235) not allowed for `java.time.LocalDateTime`");
}
}

// [modules-java8#80]: handle case-insensitive date/time
@Test
public void testDeserializationCaseInsensitiveEnabledOnValue() throws Throwable
{
ObjectMapper mapper = newMapper();
Value format = JsonFormat.Value
.forPattern("dd-MMM-yyyy HH:mm")
.withFeature(Feature.ACCEPT_CASE_INSENSITIVE_VALUES);
mapper.configOverride(LocalDateTime.class).setFormat(format);
ObjectReader reader = mapper.readerFor(LocalDateTime.class);
String[] jsons = new String[] {"'01-Jan-2000 13:14'","'01-JAN-2000 13:14'", "'01-jan-2000 13:14'"};
for(String json : jsons) {
expectSuccess(reader, LocalDateTime.of(2000, Month.JANUARY, 1, 13, 14), json);
}
}

@Test
public void testDeserializationCaseInsensitiveEnabled() throws Throwable
{
ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, true);
mapper.configOverride(LocalDateTime.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy HH:mm"));
ObjectReader reader = mapper.readerFor(LocalDateTime.class);
String[] jsons = new String[] {"'01-Jan-2000 13:45'","'01-JAN-2000 13:45'", "'01-jan-2000 13:45'"};
for(String json : jsons) {
expectSuccess(reader, LocalDateTime.of(2000, Month.JANUARY, 1, 13, 45), json);
}
}

@Test
public void testDeserializationCaseInsensitiveDisabled() throws Throwable
{
ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false);
mapper.configOverride(LocalDateTime.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy HH:mm"));
ObjectReader reader = mapper.readerFor(LocalDateTime.class);
expectSuccess(reader, LocalDateTime.of(2000, Month.JANUARY, 1, 13, 45), "'01-Jan-2000 13:45'");
}

@Test
public void testDeserializationCaseInsensitiveDisabled_InvalidDate() throws Throwable
{
ObjectMapper mapper = newMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES, false);
mapper.configOverride(LocalDateTime.class).setFormat(JsonFormat.Value.forPattern("dd-MMM-yyyy"));
ObjectReader reader = mapper.readerFor(LocalDateTime.class);
String[] jsons = new String[] {"'01-JAN-2000'", "'01-jan-2000'"};
for(String json : jsons) {
expectFailure(reader, json);
}
}

private void expectSuccess(ObjectReader reader, Object exp, String json) throws IOException {
final LocalDateTime value = read(reader, json);
notNull(value);
expect(exp, value);
}

private void expectFailure(ObjectReader reader, String json) throws Throwable {
try {
read(reader, json);
fail("expected DateTimeParseException");
} catch (JsonProcessingException e) {
if (e.getCause() == null) {
throw e;
}
if (!(e.getCause() instanceof DateTimeParseException)) {
throw e.getCause();
}
} catch (IOException e) {
throw e;
}
}

private LocalDateTime read(ObjectReader reader, final String json) throws IOException {
return reader.readValue(aposToQuotes(json));
}

private void notNull(Object value) {
assertNotNull("The value should not be null.", value);
}

private void expect(Object exp, Object value) {
assertEquals("The value is not correct.", exp, value);
}
}
1 change: 0 additions & 1 deletion parameter-names/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ introspection of method/constructor parameter names, without having to add expli
<optimize>true</optimize>
<compilerArgs>
<arg>-Xlint</arg>
<arg>-Werror</arg>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
Expand Down