diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java index b5da614..8a17c58 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java @@ -25,6 +25,12 @@ public class GuavaDeserializers extends Deserializers.Base { + private BoundType _defaultBoundType; + + public GuavaDeserializers(BoundType defaultBoundType) { + _defaultBoundType = defaultBoundType; + } + /** * We have plenty of collection types to support... */ @@ -251,7 +257,7 @@ public JsonDeserializer findBeanDeserializer(final JavaType type, Deserializa return new GuavaOptionalDeserializer(type, refType, typeDeser, valueDeser); } if (raw == Range.class) { - return new RangeDeserializer(type); + return new RangeDeserializer(_defaultBoundType, type); } if (raw == HostAndPort.class) { return HostAndPortDeserializer.std; diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java index cd84f79..8219dc9 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java +++ b/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaModule.java @@ -3,7 +3,11 @@ import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.cfg.PackageVersion; import com.fasterxml.jackson.datatype.guava.ser.GuavaBeanSerializerModifier; +import com.google.common.collect.BoundType; + +import static com.google.common.base.Preconditions.checkNotNull; /** * Basic Jackson {@link Module} that adds support for Guava types. @@ -37,6 +41,7 @@ public class GuavaModule extends Module // can't use just SimpleModule, due to g * changes after registration will have no effect. */ protected boolean _cfgHandleAbsentAsNull = true; + protected BoundType _defaultBoundType; public GuavaModule() { super(); @@ -48,7 +53,7 @@ public GuavaModule() { @Override public void setupModule(SetupContext context) { - context.addDeserializers(new GuavaDeserializers()); + context.addDeserializers(new GuavaDeserializers(_defaultBoundType)); context.addSerializers(new GuavaSerializers()); context.addTypeModifier(new GuavaTypeModifier()); @@ -75,6 +80,24 @@ public GuavaModule configureAbsentsAsNulls(boolean state) { _cfgHandleAbsentAsNull = state; return this; } + + /** + * Configuration method that may be used to change the {@link BoundType} to be used + * when deserializing {@link com.google.common.collect.Range} objects. This configuration + * will is used when the object to be deserialied has no bound type attribute. + * The default {@link BoundType} is CLOSED. + * + * @param boundType {@link BoundType} + * + * @return This module instance, useful for chaining calls + * + * @since 2.6.1 ? FIXME + */ + public GuavaModule defaultBoundType(BoundType boundType) { + checkNotNull(boundType); + _defaultBoundType = boundType; + return this; + } @Override public int hashCode() diff --git a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java index 7f8a74d..13853f4 100644 --- a/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/datatype/guava/deser/RangeDeserializer.java @@ -33,14 +33,17 @@ public class RangeDeserializer protected final JsonDeserializer _endpointDeserializer; + private BoundType _defaultBoundType; + /* /********************************************************** /* Life-cycle /********************************************************** */ - public RangeDeserializer(JavaType rangeType) { + public RangeDeserializer(BoundType defaultBoundType, JavaType rangeType) { this(rangeType, null); + this._defaultBoundType = defaultBoundType; } @SuppressWarnings("unchecked") @@ -51,6 +54,15 @@ public RangeDeserializer(JavaType rangeType, JsonDeserializer endpointDeser) _endpointDeserializer = (JsonDeserializer) endpointDeser; } + @SuppressWarnings("unchecked") + public RangeDeserializer(JavaType rangeType, JsonDeserializer endpointDeser, BoundType defaultBoundType) + { + super(rangeType); + _rangeType = rangeType; + _endpointDeserializer = (JsonDeserializer) endpointDeser; + _defaultBoundType = defaultBoundType; + } + @Override public JavaType getValueType() { return _rangeType; } @@ -64,7 +76,7 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, endpointType = TypeFactory.unknownType(); } JsonDeserializer deser = ctxt.findContextualValueDeserializer(endpointType, property); - return new RangeDeserializer(_rangeType, deser); + return new RangeDeserializer(_rangeType, deser, _defaultBoundType); } return this; } @@ -126,6 +138,12 @@ public Range deserialize(JsonParser parser, DeserializationContext context) } } + if (lowerBoundType == null) + lowerBoundType = _defaultBoundType; + + if (upperBoundType == null) + upperBoundType = _defaultBoundType; + try { if ((lowerEndpoint != null) && (upperEndpoint != null)) { Preconditions.checkState(lowerEndpoint.getClass() == upperEndpoint.getClass(), diff --git a/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java b/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java index d1f63bc..873761f 100644 --- a/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java +++ b/src/test/java/com/fasterxml/jackson/datatype/guava/TestRange.java @@ -2,9 +2,11 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.guava.deser.util.RangeFactory; +import com.google.common.collect.BoundType; import com.google.common.collect.Range; import java.io.IOException; @@ -103,4 +105,148 @@ public void testUntyped() throws Exception assertNotNull(out); assertEquals(Range.class, out.range.getClass()); } + + public void testDefaultBoundTypeNoBoundTypeInformed() throws Exception + { + String json = "{\"lowerEndpoint\": 2, \"upperEndpoint\": 3}"; + + try { + MAPPER.readValue(json, Range.class); + fail("Should have failed"); + } catch (JsonMappingException e) { + verifyException(e, "'lowerEndpoint' field found, but not 'lowerBoundType'"); + } + } + + public void testDefaultBoundTypeNoBoundTypeInformedWithClosedConfigured() throws Exception + { + String json = "{\"lowerEndpoint\": 2, \"upperEndpoint\": 3}"; + + GuavaModule mod = new GuavaModule().defaultBoundType(BoundType.CLOSED); + ObjectMapper mapper = new ObjectMapper().registerModule(mod); + + @SuppressWarnings("unchecked") + Range r = (Range) mapper.readValue(json, Range.class); + + assertEquals(Integer.valueOf(2), r.lowerEndpoint()); + assertEquals(Integer.valueOf(3), r.upperEndpoint()); + assertEquals(BoundType.CLOSED, r.lowerBoundType()); + assertEquals(BoundType.CLOSED, r.upperBoundType()); + } + + public void testDefaultBoundTypeOnlyLowerBoundTypeInformed() throws Exception + { + String json = "{\"lowerEndpoint\": 2, \"lowerBoundType\": \"OPEN\", \"upperEndpoint\": 3}"; + + try { + MAPPER.readValue(json, Range.class); + fail("Should have failed"); + } catch (JsonMappingException e) { + verifyException(e, "'upperEndpoint' field found, but not 'upperBoundType'"); + } + } + + public void testDefaultBoundTypeOnlyLowerBoundTypeInformedWithClosedConfigured() throws Exception + { + String json = "{\"lowerEndpoint\": 2, \"lowerBoundType\": \"OPEN\", \"upperEndpoint\": 3}"; + + GuavaModule mod = new GuavaModule().defaultBoundType(BoundType.CLOSED); + ObjectMapper mapper = new ObjectMapper().registerModule(mod); + + @SuppressWarnings("unchecked") + Range r = (Range) mapper.readValue(json, Range.class); + + assertEquals(Integer.valueOf(2), r.lowerEndpoint()); + assertEquals(Integer.valueOf(3), r.upperEndpoint()); + assertEquals(BoundType.OPEN, r.lowerBoundType()); + assertEquals(BoundType.CLOSED, r.upperBoundType()); + } + + public void testDefaultBoundTypeOnlyUpperBoundTypeInformed() throws Exception + { + String json = "{\"lowerEndpoint\": 2, \"upperEndpoint\": 3, \"upperBoundType\": \"OPEN\"}"; + + try { + MAPPER.readValue(json, Range.class); + fail("Should have failed"); + } catch (JsonMappingException e) { + verifyException(e, "'lowerEndpoint' field found, but not 'lowerBoundType'"); + } + } + + public void testDefaultBoundTypeOnlyUpperBoundTypeInformedWithClosedConfigured() throws Exception + { + String json = "{\"lowerEndpoint\": 1, \"upperEndpoint\": 3, \"upperBoundType\": \"OPEN\"}"; + + GuavaModule mod = new GuavaModule().defaultBoundType(BoundType.CLOSED); + ObjectMapper mapper = new ObjectMapper().registerModule(mod); + + @SuppressWarnings("unchecked") + Range r = (Range) mapper.readValue(json, Range.class); + + assertEquals(Integer.valueOf(1), r.lowerEndpoint()); + assertEquals(Integer.valueOf(3), r.upperEndpoint()); + assertEquals(BoundType.CLOSED, r.lowerBoundType()); + assertEquals(BoundType.OPEN, r.upperBoundType()); + } + + public void testDefaultBoundTypeBothBoundTypesOpen() throws Exception + { + String json = "{\"lowerEndpoint\": 2, \"lowerBoundType\": \"OPEN\", \"upperEndpoint\": 3, \"upperBoundType\": \"OPEN\"}"; + @SuppressWarnings("unchecked") + Range r = (Range) MAPPER.readValue(json, Range.class); + + assertEquals(Integer.valueOf(2), r.lowerEndpoint()); + assertEquals(Integer.valueOf(3), r.upperEndpoint()); + + assertEquals(BoundType.OPEN, r.lowerBoundType()); + assertEquals(BoundType.OPEN, r.upperBoundType()); + } + + public void testDefaultBoundTypeBothBoundTypesOpenWithClosedConfigured() throws Exception + { + String json = "{\"lowerEndpoint\": 1, \"lowerBoundType\": \"OPEN\", \"upperEndpoint\": 3, \"upperBoundType\": \"OPEN\"}"; + + GuavaModule mod = new GuavaModule().defaultBoundType(BoundType.CLOSED); + ObjectMapper mapper = new ObjectMapper().registerModule(mod); + + @SuppressWarnings("unchecked") + Range r = (Range) mapper.readValue(json, Range.class); + + assertEquals(Integer.valueOf(1), r.lowerEndpoint()); + assertEquals(Integer.valueOf(3), r.upperEndpoint()); + + assertEquals(BoundType.OPEN, r.lowerBoundType()); + assertEquals(BoundType.OPEN, r.upperBoundType()); + } + + public void testDefaultBoundTypeBothBoundTypesClosed() throws Exception + { + String json = "{\"lowerEndpoint\": 1, \"lowerBoundType\": \"CLOSED\", \"upperEndpoint\": 3, \"upperBoundType\": \"CLOSED\"}"; + @SuppressWarnings("unchecked") + Range r = (Range) MAPPER.readValue(json, Range.class); + + assertEquals(Integer.valueOf(1), r.lowerEndpoint()); + assertEquals(Integer.valueOf(3), r.upperEndpoint()); + + assertEquals(BoundType.CLOSED, r.lowerBoundType()); + assertEquals(BoundType.CLOSED, r.upperBoundType()); + } + + public void testDefaultBoundTypeBothBoundTypesClosedWithOpenConfigured() throws Exception + { + String json = "{\"lowerEndpoint\": 12, \"lowerBoundType\": \"CLOSED\", \"upperEndpoint\": 33, \"upperBoundType\": \"CLOSED\"}"; + + GuavaModule mod = new GuavaModule().defaultBoundType(BoundType.CLOSED); + ObjectMapper mapper = new ObjectMapper().registerModule(mod); + + @SuppressWarnings("unchecked") + Range r = (Range) mapper.readValue(json, Range.class); + + assertEquals(Integer.valueOf(12), r.lowerEndpoint()); + assertEquals(Integer.valueOf(33), r.upperEndpoint()); + + assertEquals(BoundType.CLOSED, r.lowerBoundType()); + assertEquals(BoundType.CLOSED, r.upperBoundType()); + } }