diff --git a/release-notes/VERSION b/release-notes/VERSION index ef4af70e2e..056f903c5b 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -116,11 +116,15 @@ Project: jackson-databind - Added new overload for `JsonSerializer.isEmpty()`, to eventually solve #588 - Improve error messaging (related to [jaxb-annotations#38]) to include known subtype ids. -2.4.6 (not yet released) +2.4.5.1 (26-Mar-2015) -#706: Add support for @JsonUnwrapper via JSON Schema module +Special one-off "micro patch" for: + +#706: Add support for `@JsonUnwrapped` via JSON Schema module #707: Error in getting string representation of an ObjectNode with a float number value (reported by @navidqar) +#735: @JsonDeserialize on Map with contentUsing custom deserializer overwrites default behavior + (reported by blackfyre512@github) (regression due to #604) 2.4.5 (13-Jan-2015) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java index 478436f5f4..424b2decb8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java @@ -45,9 +45,9 @@ public class CollectionDeserializer protected final TypeDeserializer _valueTypeDeserializer; // // Instance construction settings: - + protected final ValueInstantiator _valueInstantiator; - + /** * Deserializer that is used iff delegate-based creator is * to be used for deserializing from JSON Object. @@ -121,7 +121,11 @@ protected CollectionDeserializer withResolved(JsonDeserializer dd, // Important: do NOT cache if polymorphic values @Override // since 2.5 public boolean isCachable() { - return (_valueTypeDeserializer == null); + // 26-Mar-2015, tatu: As per [databind#735], need to be careful + return (_valueDeserializer == null) + && (_valueTypeDeserializer == null) + && (_delegateDeserializer == null) + ; } /* @@ -152,6 +156,7 @@ public CollectionDeserializer createContextual(DeserializationContext ctxt, } // also, often value deserializer is resolved here: JsonDeserializer valueDeser = _valueDeserializer; + // #125: May have a content converter valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser); final JavaType vt = _collectionType.getContentType(); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java index ef1740a63c..2e6ce5ff6a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java @@ -94,7 +94,9 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanPro @Override public boolean isCachable() { // Important: do NOT cache if polymorphic values - return (_valueTypeDeserializer == null); + return (_valueDeserializer == null) + && (_keyDeserializer == null) + && (_valueTypeDeserializer == null); } /* diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java index bae969f03c..d386e8ef3b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java @@ -54,7 +54,13 @@ public EnumSetDeserializer withDeserializer(JsonDeserializer deser) { * let's cache instances by default. */ @Override - public boolean isCachable() { return true; } + public boolean isCachable() { + // One caveat: content deserializer should prevent caching + if (_enumType.getValueHandler() != null) { + return false; + } + return true; + } @Override public JsonDeserializer createContextual(DeserializationContext ctxt, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java index f59e4bfa7a..9c61c93f5c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java @@ -303,7 +303,13 @@ public JsonDeserializer getContentDeserializer() { */ @Override public boolean isCachable() { - return (_valueTypeDeserializer == null) && (_ignorableProperties == null); + /* As per [databind#735], existence of value or key deserializer (only passed + * if annotated to use non-standard one) should also prevent caching. + */ + return (_valueDeserializer == null) + && (_keyDeserializer == null) + && (_valueTypeDeserializer == null) + && (_ignorableProperties == null); } @Override diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java index 7916f6816b..0ff61260cd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java @@ -104,8 +104,8 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, @Override // since 2.5 public boolean isCachable() { - // Important: do NOT cache if polymorphic values - return (_elementTypeDeserializer == null); + // Important: do NOT cache if polymorphic values, or ones with custom deserializer + return (_elementDeserializer == null) && (_elementTypeDeserializer == null); } /* diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java index 143070d188..8b1267d239 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java @@ -84,7 +84,8 @@ protected StringCollectionDeserializer withResolved(JsonDeserializer delegate @Override // since 2.5 public boolean isCachable() { - return true; + // 26-Mar-2015, tatu: Important: prevent caching if custom deserializers are involved + return (_valueDeserializer == null) && (_delegateDeserializer == null); } /* diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java index 7333f16684..8ddd1e2c02 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java @@ -165,7 +165,13 @@ protected JsonDeserializer _withResolved(JsonDeserializer mapDeser, * since it's not uncommon to "read anything" */ @Override - public boolean isCachable() { return true; } + public boolean isCachable() { + /* 26-Mar-2015, tatu: With respect to [databind#735], there are concerns over + * cachability. It seems like we SHOULD be safe here; but just in case there + * are problems with false sharing, this may need to be revisited. + */ + return true; + } @Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java index dfa8fedb41..e25d8a8a83 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestCustomDeserializers.java @@ -226,7 +226,22 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanPro } return this; } - + } + + // For [databind#735] + public static class TestMapBean735 { + + @JsonDeserialize(contentUsing = CustomDeserializer735.class) + public Map map1; + + public Map map2; + } + + public static class TestListBean735 { + public List list1; + + @JsonDeserialize(contentUsing = CustomDeserializer735.class) + public List list100; } // for [databind#631] @@ -235,7 +250,18 @@ static class Issue631Bean @JsonDeserialize(using=ParentClassDeserializer.class) public Object prop; } - + + public static class CustomDeserializer735 extends StdDeserializer { + public CustomDeserializer735() { + super(Integer.class); + } + + @Override + public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return 100 * p.getValueAsInt(); + } + } + static class ParentClassDeserializer extends StdScalarDeserializer { @@ -249,7 +275,7 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) Object parent = p.getCurrentValue(); String desc = (parent == null) ? "NULL" : parent.getClass().getSimpleName(); return "prop/"+ desc; - } + } } /* @@ -382,4 +408,21 @@ public void testCurrentValueAccess() throws Exception assertNotNull(bean); assertEquals("prop/Issue631Bean", bean.prop); } + + // [databind#735]: erroneous application of custom deserializer + public void testCustomMapValueDeser735() throws Exception { + String json = "{\"map1\":{\"a\":1},\"map2\":{\"a\":1}}"; + TestMapBean735 bean = MAPPER.readValue(json, TestMapBean735.class); + + assertEquals(100, bean.map1.get("a").intValue()); + assertEquals(1, bean.map2.get("a").intValue()); + } + + public void testCustomListValueDeser735() throws Exception { + String json = "{\"list1\":[1],\"list100\":[1]}"; + TestListBean735 bean = MAPPER.readValue(json, TestListBean735.class); + + assertEquals(1, bean.list1.get(0).intValue()); + assertEquals(100, bean.list100.get(0).intValue()); + } }