Skip to content

Commit

Permalink
Fix #281: support new JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Nov 12, 2023
1 parent 79d0379 commit 0b3bd94
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,13 @@ public void setupModule(SetupContext context) {

SimpleDeserializers desers = new SimpleDeserializers();
// // Instant variants:
desers.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
desers.addDeserializer(OffsetDateTime.class, InstantDeserializer.OFFSET_DATE_TIME);
desers.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);
desers.addDeserializer(Instant.class,
InstantDeserializer.INSTANT.withFeatures(_features));
desers.addDeserializer(OffsetDateTime.class,
InstantDeserializer.OFFSET_DATE_TIME.withFeatures(_features));
desers.addDeserializer(ZonedDateTime.class,
InstantDeserializer.ZONED_DATE_TIME.withFeatures(_features));

// // Other deserializers
desers.addDeserializer(Duration.class, DurationDeserializer.INSTANCE);
desers.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;

import java.io.IOException;
import java.math.BigDecimal;
Expand Down Expand Up @@ -54,6 +56,8 @@ public class InstantDeserializer<T extends Temporal>
{
private static final long serialVersionUID = 1L;

private final static boolean DEFAULT_NORMALIZE_ZONE_ID = JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID.enabledByDefault();

/**
* Constants used to check if ISO 8601 time string is colonless. See [jackson-modules-java8#131]
*
Expand All @@ -68,7 +72,7 @@ public class InstantDeserializer<T extends Temporal>
a -> Instant.ofEpochSecond(a.integer, a.fraction),
null,
true, // yes, replace zero offset with Z
true // default: yes, normalize ZoneId
DEFAULT_NORMALIZE_ZONE_ID
);

public static final InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new InstantDeserializer<>(
Expand All @@ -78,7 +82,7 @@ public class InstantDeserializer<T extends Temporal>
a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
(d, z) -> (d.isEqual(OffsetDateTime.MIN) || d.isEqual(OffsetDateTime.MAX) ? d : d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime()))),
true, // yes, replace zero offset with Z
true // default: yes, normalize ZoneId
DEFAULT_NORMALIZE_ZONE_ID
);

public static final InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new InstantDeserializer<>(
Expand All @@ -88,7 +92,7 @@ public class InstantDeserializer<T extends Temporal>
a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
ZonedDateTime::withZoneSameInstant,
false, // keep zero offset and Z separate since zones explicitly supported
true // default: yes, normalize ZoneId
DEFAULT_NORMALIZE_ZONE_ID
);

protected final Function<FromIntegerArguments, T> fromMilliseconds;
Expand Down Expand Up @@ -212,6 +216,26 @@ protected InstantDeserializer(InstantDeserializer<T> base,
_normalizeZoneId = base._normalizeZoneId;
}

/**
* @since 2.16
*/
@SuppressWarnings("unchecked")
protected InstantDeserializer(InstantDeserializer<T> base,
JacksonFeatureSet<JavaTimeFeature> features)
{
super((Class<T>) base.handledType(), base._formatter);
parsedToValue = base.parsedToValue;
fromMilliseconds = base.fromMilliseconds;
fromNanoseconds = base.fromNanoseconds;
adjust = base.adjust;
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
_adjustToContextTZOverride = base._adjustToContextTZOverride;
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;

_normalizeZoneId = features.isEnabled(JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID);

}

@Override
protected InstantDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
if (dtf == _formatter) {
Expand All @@ -225,6 +249,14 @@ protected InstantDeserializer<T> withLeniency(Boolean leniency) {
return new InstantDeserializer<>(this, _formatter, leniency);
}

// @since 2.16
public InstantDeserializer<T> withFeatures(JacksonFeatureSet<JavaTimeFeature> features) {
if (_normalizeZoneId == features.isEnabled(JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID)) {
return this;
}
return new InstantDeserializer<>(this, features);
}

@SuppressWarnings("unchecked")
@Override // @since 2.12.1
protected JSR310DateTimeDeserializerBase<?> _withFormatOverrides(DeserializationContext ctxt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;

import org.junit.Test;
Expand All @@ -21,6 +24,7 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Map;
import java.util.TimeZone;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
Expand All @@ -29,6 +33,12 @@
public class ZonedDateTimeDeserTest extends ModuleTestBase
{
private final ObjectReader READER = newMapper().readerFor(ZonedDateTime.class);

private final ObjectReader READER_NON_NORMALIZED_ZONEID = JsonMapper.builder()
.addModule(new JavaTimeModule().disable(JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID))
.build()
.readerFor(ZonedDateTime.class);

private final TypeReference<Map<String, ZonedDateTime>> MAP_TYPE_REF = new TypeReference<Map<String, ZonedDateTime>>() { };

static class WrapperWithFeatures {
Expand Down Expand Up @@ -57,13 +67,24 @@ public WrapperWithReadTimestampsAsNanosEnabled() { }
}

@Test
public void testDeserializationAsString01() throws Exception
public void testDeserFromString() throws Exception
{
assertEquals("The value is not correct.",
ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC),
READER.readValue(q("2000-01-01T12:00Z")));
}

// [modules-java#281]
@Test
public void testDeserFromStringNoZoneIdNormalization() throws Exception
{
// 11-Nov-2023, tatu: Not sure this is great test but... does show diff
// behavior with and without `JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID`
assertEquals("The value is not correct.",
ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, TimeZone.getTimeZone("UTC").toZoneId()),
READER_NON_NORMALIZED_ZONEID.readValue(q("2000-01-01T12:00Z")));
}

@Test
public void testDeserializationAsInt01() throws Exception
{
Expand Down
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Modules:
#272: (datetime) `JsonFormat.Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS`
not respected when deserialising `Instant`s
(fix contributed by Raman B)
#281: (datetime) Add `JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID` to allow
disabling ZoneId normalization on deserialization
(requested by @indyana)

2.15.3 (12-Oct-2023)
2.15.2 (30-May-2023)
Expand Down

0 comments on commit 0b3bd94

Please sign in to comment.