diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index b64a0527ee..bfd29fa486 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -13,12 +13,12 @@ import com.fasterxml.jackson.core.filter.TokenFilter.Inclusion; import com.fasterxml.jackson.core.type.ResolvedType; import com.fasterxml.jackson.core.type.TypeReference; - import com.fasterxml.jackson.databind.cfg.ContextAttributes; import com.fasterxml.jackson.databind.cfg.DatatypeFeature; import com.fasterxml.jackson.databind.deser.DataFormatReaders; import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext; import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; +import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.TreeTraversingParser; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -77,7 +77,7 @@ public class ObjectReader protected final boolean _unwrapRoot; /** - * Filter to be consider for JsonParser. + * Filter to be consider for JsonParser. * Default value to be null as filter not considered. */ private final TokenFilter _filter; @@ -192,7 +192,7 @@ protected ObjectReader(ObjectMapper mapper, DeserializationConfig config, _unwrapRoot = config.useRootWrapping(); _rootDeserializer = _prefetchRootDeserializer(valueType); - _dataFormatReaders = null; + _dataFormatReaders = null; _filter = null; } @@ -401,7 +401,7 @@ public ObjectReader with(DeserializationFeature first, DeserializationFeature... other) { return _with(_config.with(first, other)); - } + } /** * Method for constructing a new reader instance that is configured @@ -409,14 +409,14 @@ public ObjectReader with(DeserializationFeature first, */ public ObjectReader withFeatures(DeserializationFeature... features) { return _with(_config.withFeatures(features)); - } + } /** * Method for constructing a new reader instance that is configured * with specified feature disabled. */ public ObjectReader without(DeserializationFeature feature) { - return _with(_config.without(feature)); + return _with(_config.without(feature)); } /** @@ -426,7 +426,7 @@ public ObjectReader without(DeserializationFeature feature) { public ObjectReader without(DeserializationFeature first, DeserializationFeature... other) { return _with(_config.without(first, other)); - } + } /** * Method for constructing a new reader instance that is configured @@ -434,7 +434,7 @@ public ObjectReader without(DeserializationFeature first, */ public ObjectReader withoutFeatures(DeserializationFeature... features) { return _with(_config.withoutFeatures(features)); - } + } /* /********************************************************************** @@ -460,7 +460,7 @@ public ObjectReader with(DatatypeFeature feature) { */ public ObjectReader withFeatures(DatatypeFeature... features) { return _with(_config.withFeatures(features)); - } + } /** * Method for constructing a new reader instance that is configured @@ -469,7 +469,7 @@ public ObjectReader withFeatures(DatatypeFeature... features) { * @since 2.14 */ public ObjectReader without(DatatypeFeature feature) { - return _with(_config.without(feature)); + return _with(_config.without(feature)); } /** @@ -480,7 +480,7 @@ public ObjectReader without(DatatypeFeature feature) { */ public ObjectReader withoutFeatures(DatatypeFeature... features) { return _with(_config.withoutFeatures(features)); - } + } /* /********************************************************** @@ -511,7 +511,7 @@ public ObjectReader with(JsonParser.Feature feature) { */ public ObjectReader withFeatures(JsonParser.Feature... features) { return _with(_config.withFeatures(features)); - } + } /** * Method for constructing a new reader instance that is configured @@ -522,7 +522,7 @@ public ObjectReader withFeatures(JsonParser.Feature... features) { * @return Reader instance with specified feature disabled */ public ObjectReader without(JsonParser.Feature feature) { - return _with(_config.without(feature)); + return _with(_config.without(feature)); } /** @@ -591,7 +591,7 @@ public ObjectReader with(FormatFeature feature) { */ public ObjectReader withFeatures(FormatFeature... features) { return _with(_config.withFeatures(features)); - } + } /** * Method for constructing a new reader instance that is configured @@ -600,7 +600,7 @@ public ObjectReader withFeatures(FormatFeature... features) { * @since 2.7 */ public ObjectReader without(FormatFeature feature) { - return _with(_config.without(feature)); + return _with(_config.without(feature)); } /** @@ -620,8 +620,8 @@ public ObjectReader withoutFeatures(FormatFeature... features) { */ /** - * Convenience method to bind from {@link JsonPointer}. - * {@link JsonPointerBasedFilter} is registered and will be used for parsing later. + * Convenience method to bind from {@link JsonPointer}. + * {@link JsonPointerBasedFilter} is registered and will be used for parsing later. * @since 2.6 */ public ObjectReader at(final String pointerExpr) { @@ -648,7 +648,7 @@ public ObjectReader at(final JsonPointer pointer) { */ public ObjectReader with(DeserializationConfig config) { return _with(config); - } + } /** * Method for constructing a new instance with configuration that uses @@ -776,7 +776,7 @@ public ObjectReader forType(JavaType valueType) } return _new(this, _config, valueType, rootDeser, _valueToUpdate, _schema, _injectableValues, det); - } + } /** * Method for constructing a new reader instance that is configured @@ -789,7 +789,7 @@ public ObjectReader forType(JavaType valueType) */ public ObjectReader forType(Class valueType) { return forType(_config.constructType(valueType)); - } + } /** * Method for constructing a new reader instance that is configured @@ -802,7 +802,7 @@ public ObjectReader forType(Class valueType) { */ public ObjectReader forType(TypeReference valueTypeRef) { return forType(_config.getTypeFactory().constructType(valueTypeRef.getType())); - } + } /** * @deprecated since 2.5 Use {@link #forType(JavaType)} instead @@ -818,7 +818,7 @@ public ObjectReader withType(JavaType valueType) { @Deprecated public ObjectReader withType(Class valueType) { return forType(_config.constructType(valueType)); - } + } /** * @deprecated since 2.5 Use {@link #forType(Class)} instead @@ -834,11 +834,11 @@ public ObjectReader withType(java.lang.reflect.Type valueType) { @Deprecated public ObjectReader withType(TypeReference valueTypeRef) { return forType(_config.getTypeFactory().constructType(valueTypeRef.getType())); - } + } /** * Method for constructing a new instance with configuration that - * updates passed Object (as root value), instead of constructing + * updates passed Object (as root value), instead of constructing * a new value. *

* Note that the method does NOT change state of this reader, but @@ -1294,6 +1294,139 @@ public T readValue(JsonParser p, JavaType valueType) throws IOException { return (T) forType(valueType).readValue(p); } + @SuppressWarnings("unchecked") + public List readValue(JsonParser p, List elementTypeList) throws IOException { + if (_valueType == null || !List.class.isAssignableFrom(_valueType.getRawClass())) { + return forType(List.class).readValue(p, elementTypeList); + } + _assertNotNull("p", p); + _assertNotNull("elementTypeList", elementTypeList); + List result; + final DefaultDeserializationContext ctxt = createDeserializationContext(p); + JsonToken t = _initForReading(ctxt, p); + if (t == JsonToken.VALUE_NULL) { + if (_valueToUpdate == null) { + result = (List) _findRootDeserializer(ctxt).getNullValue(ctxt); + } else { + result = (List)_valueToUpdate; + } + } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { + result = (List) _valueToUpdate; + } else { // pointing to event other than null + CollectionDeserializer deser = + (CollectionDeserializer) ((JsonDeserializer) _findRootDeserializer(ctxt)); + if (_config.useRootWrapping()) { + return _unwrapAndDeserializeWithElementTypeList( + p, ctxt, _valueType, deser, _valueToUpdate, elementTypeList); + } + if (_valueToUpdate == null) { + return deserializeList(deser, p, ctxt, elementTypeList); + } + return deserializeList(deser, p, ctxt, (List) _valueToUpdate, elementTypeList); + } + // Need to consume the token too + p.clearCurrentToken(); + if (_config.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) { + _verifyNoTrailingTokens(p, ctxt, _valueType); + } + return result; + } + + @SuppressWarnings("unchecked") + protected List _unwrapAndDeserializeWithElementTypeList(JsonParser p, + DefaultDeserializationContext ctxt, + JavaType rootType, CollectionDeserializer deser, + Object valueToUpdate, List elementTypeList) + throws IOException + { + PropertyName expRootName = _config.findRootName(rootType); + String expSimpleName = expRootName.getSimpleName(); + if (p.currentToken() != JsonToken.START_OBJECT) { + ctxt.reportWrongTokenException(rootType, JsonToken.START_OBJECT, + "Current token not START_OBJECT (needed to unwrap root name %s), but %s", + ClassUtil.name(expSimpleName), p.currentToken()); + } + if (p.nextToken() != JsonToken.FIELD_NAME) { + ctxt.reportWrongTokenException(rootType, JsonToken.FIELD_NAME, + "Current token not FIELD_NAME (to contain expected root name %s), but %s", + ClassUtil.name(expSimpleName), p.currentToken()); + } + String actualName = p.currentName(); + if (!expSimpleName.equals(actualName)) { + ctxt.reportPropertyInputMismatch(rootType, actualName, + "Root name (%s) does not match expected (%s) for type %s", + ClassUtil.name(actualName), ClassUtil.name(expSimpleName), + ClassUtil.getTypeDescription(rootType)); + } + p.nextToken(); + final List result; + if (valueToUpdate == null) { + result = deserializeList(deser, p, ctxt, elementTypeList); + } else { + result = deserializeList(deser, p, ctxt, (List) valueToUpdate, elementTypeList); + } + if (p.nextToken() != JsonToken.END_OBJECT) { + ctxt.reportWrongTokenException(rootType, JsonToken.END_OBJECT, + "Current token not END_OBJECT (to match wrapper object with root name %s), but %s", + ClassUtil.name(expSimpleName), p.currentToken()); + } + return result; + } + + @SuppressWarnings("unchecked") + protected List deserializeList(CollectionDeserializer deser, JsonParser p, + DeserializationContext ctxt, List elemenTypeList) + throws IOException + { + if (p.isExpectedStartArrayToken()) { + List result = (List) deser.getValueInstantiator().createUsingDefault(ctxt); + return _deserializeFromArrayWithElementTypeList(p, ctxt, result, elemenTypeList); + } + return (List) ctxt.handleUnexpectedToken(deser.getValueType(), p); + } + + @SuppressWarnings("unchecked") + protected List deserializeList(CollectionDeserializer deser, JsonParser p, + DeserializationContext ctxt, List result, List elemenTypeList) + throws IOException + { + if (p.isExpectedStartArrayToken()) { + return _deserializeFromArrayWithElementTypeList(p, ctxt, result, elemenTypeList); + } + return (List) ctxt.handleUnexpectedToken(deser.getValueType(), p); + } + + protected List _deserializeFromArrayWithElementTypeList(JsonParser p, + DeserializationContext ctxt, List result, List elemenTypeList) + throws IOException + { + p.setCurrentValue(result); + + JsonDeserializer valueDes = null; + JsonToken t; + int idx = -1; + while ((t = p.nextToken()) != JsonToken.END_ARRAY) { + try { + idx++; + valueDes = ctxt.findContextualValueDeserializer(elemenTypeList.get(idx), null); + Object value; + if (t == JsonToken.VALUE_NULL) { + value = null; + } else { + value = valueDes.deserialize(p, ctxt); + } + result.add(value); + } catch (Exception e) { + boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS); + if (!wrap) { + ClassUtil.throwIfRTE(e); + } + throw JsonMappingException.wrapWithPath(e, result, result.size()); + } + } + return result; + } + /** * Convenience method that is equivalent to: *
@@ -2208,7 +2341,7 @@ protected  MappingIterator _bindAndReadValues(JsonParser p) throws IOExcep
     }
 
     /**
-     * Consider filter when creating JsonParser.  
+     * Consider filter when creating JsonParser.
      */
     protected JsonParser _considerFilter(final JsonParser p, boolean multiValue) {
         // 26-Mar-2016, tatu: Need to allow multiple-matches at least if we have
@@ -2259,7 +2392,7 @@ protected Object _detectBindAndClose(DataFormatReaders.Match match, boolean forc
             _reportUnkownFormat(_dataFormatReaders, match);
         }
         JsonParser p = match.createParserWithMatch();
-        // One more thing: we Own the input stream now; and while it's 
+        // One more thing: we Own the input stream now; and while it's
         // not super clean way to do it, we must ensure closure so:
         if (forceClosing) {
             p.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
@@ -2275,7 +2408,7 @@ protected  MappingIterator _detectBindAndReadValues(DataFormatReaders.Matc
             _reportUnkownFormat(_dataFormatReaders, match);
         }
         JsonParser p = match.createParserWithMatch();
-        // One more thing: we Own the input stream now; and while it's 
+        // One more thing: we Own the input stream now; and while it's
         // not super clean way to do it, we must ensure closure so:
         if (forceClosing) {
             p.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java
index 706d5189e4..26866e41c4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java
@@ -2,17 +2,20 @@
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.math.BigDecimal;
 import java.util.*;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
-
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.core.*;
 import com.fasterxml.jackson.core.exc.StreamReadException;
 import com.fasterxml.jackson.core.json.JsonReadFeature;
-
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.cfg.ContextAttributes;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
 import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
 import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
 import com.fasterxml.jackson.databind.exc.MismatchedInputException;
 import com.fasterxml.jackson.databind.json.JsonMapper;
@@ -414,7 +417,7 @@ public void testPointerWithArrays() throws Exception
     public static class Pojo1637 {
         public Set set1;
         public Set set2;
-    }    
+    }
 
     // [databind#2636]
     public void testCanPassResultToOverloadedMethod() throws Exception {
@@ -532,4 +535,193 @@ private A(@JsonProperty("knownField") String knownField) {
             this.knownField = knownField;
         }
     }
+
+    @SuppressWarnings("unchecked")
+    public void testReaderForFixedElementTypes() throws IOException {
+        List elementTypeList = new ArrayList<>();
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(Integer.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(BigDecimal.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(Long.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(String.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructParametricType(Generic.class, BigDecimal.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructCollectionType(List.class, Shape.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(TestBean.class));
+        String content = "[]";
+        JsonParser p = MAPPER.createParser(content);
+        ObjectReader listObjectReader = MAPPER.readerFor(List.class);
+        List objectList = listObjectReader.readValue(p, elementTypeList);
+        p.close();
+        assertEquals(0, objectList.size());
+        content = "[null,null,null,null,null]";
+        p = MAPPER.createParser(content);
+        listObjectReader = MAPPER.readerFor(ArrayList.class);
+        objectList = listObjectReader.readValue(p, elementTypeList);
+        p.close();
+        assertEquals(5, objectList.size());
+        content = "[1,2,3,null,{\"s\":1.23},"
+                + "[{\"@class\":\"" + getClass().getCanonicalName() + "$Circle\","
+                + "\"radius\":4},"
+                + "{\"@class\":\"" + getClass().getCanonicalName() + "$Rectangle\","
+                + "\"width\":5,\"height\":6}],"
+                + "{\"map1\":{\"a\":1},\"map2\":{\"a\":1}}]";
+        p = MAPPER.createParser(content);
+        objectList = listObjectReader.readValue(p, elementTypeList);
+        p.close();
+        assertEquals(new Integer(1), objectList.get(0));
+        assertEquals(new BigDecimal("2"), objectList.get(1));
+        assertEquals(new Long(3), objectList.get(2));
+        assertEquals(null, objectList.get(3));
+        assertEquals(new BigDecimal("1.23"), ((Generic) objectList.get(4)).getT());
+        assertEquals(4, ((Circle) ((List) objectList.get(5)).get(0)).getRadius());
+        assertEquals(5, ((Rectangle) ((List) objectList.get(5)).get(1)).getWidth());
+        assertEquals(6, ((Rectangle) ((List) objectList.get(5)).get(1)).getHeight());
+        assertEquals(100, ((TestBean) objectList.get(6)).getMap1().get("a").intValue());
+        assertEquals(1, ((TestBean) objectList.get(6)).getMap2().get("a").intValue());
+        elementTypeList.clear();
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(Integer.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(BigDecimal.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(Long.class));
+        elementTypeList.add(MAPPER.getTypeFactory().constructType(String.class));
+        content = "[1,2,3,null]";
+        p = MAPPER.createParser(content);
+        objectList = listObjectReader.readValue(p, elementTypeList);
+        p.close();
+        assertEquals(new Integer(1), objectList.get(0));
+        assertEquals(new BigDecimal("2"), objectList.get(1));
+        assertEquals(new Long(3), objectList.get(2));
+    }
+
+    @JsonDeserialize(using = Generic.CustomerDeserializer.class)
+    private static class Generic {
+        private T t;
+
+        public T getT() {
+            return t;
+        }
+
+        public void setT(T t) {
+            this.t = t;
+        }
+
+        private static class CustomerDeserializer extends JsonDeserializer>
+                implements ContextualDeserializer {
+
+            private Class tClazz;
+
+            @SuppressWarnings("unused")
+            public CustomerDeserializer() {
+            }
+
+            public CustomerDeserializer(Class tClazz) {
+                this.tClazz = tClazz;
+            }
+
+            @Override
+            public Generic deserialize(JsonParser p, DeserializationContext ctxt)
+                    throws IOException, JacksonException {
+                JsonNode node = ctxt.readTree(p).findValue("s");
+                Generic g = new Generic<>();
+                Object t = ((ObjectMapper) p.getCodec()).convertValue(node, this.tClazz);
+                g.setT(t);
+                return g;
+            }
+
+            @Override
+            public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property)
+                    throws JsonMappingException {
+                JavaType currentType = null;
+                if (property == null) {
+                    // current type is root type.
+                    currentType = ctxt.getContextualType();
+                } else {
+                    // current type is wrapped in other type.
+                    currentType = property.getType();
+                }
+                Class tClazz = currentType.getBindings().getBoundType(0).getRawClass();
+                return new CustomerDeserializer(tClazz);
+            }
+        }
+    }
+
+    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+    private static class Shape {
+
+    }
+
+    private static class Circle extends Shape {
+        private int radius;
+
+        public int getRadius() {
+            return radius;
+        }
+
+        @SuppressWarnings("unused")
+        public void setRadius(int radius) {
+            this.radius = radius;
+        }
+    }
+
+    private static class Rectangle extends Shape {
+        private int width;
+        private int height;
+
+        public int getWidth() {
+            return width;
+        }
+
+        @SuppressWarnings("unused")
+        public void setWidth(int width) {
+            this.width = width;
+        }
+
+        public int getHeight() {
+            return height;
+        }
+
+        @SuppressWarnings("unused")
+        public void setHeight(int height) {
+            this.height = height;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static class TestBean {
+
+        @JsonProperty("map1")
+        @JsonDeserialize(contentUsing = TestBeanCustomDeserializer.class)
+        Map map1;
+
+        @JsonProperty("map2")
+        Map map2;
+
+        public Map getMap1() {
+            return map1;
+        }
+
+        public void setMap1(Map map1) {
+            this.map1 = map1;
+        }
+
+        public Map getMap2() {
+            return map2;
+        }
+
+        public void setMap2(Map map2) {
+            this.map2 = map2;
+        }
+    }
+
+    private static class TestBeanCustomDeserializer extends StdDeserializer {
+        private static final long serialVersionUID = 1L;
+
+        public TestBeanCustomDeserializer() {
+            super(Integer.class);
+        }
+
+        @Override
+        public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+            Integer value = p.readValueAs(Integer.class);
+            return value * 100;
+        }
+    }
 }