Implemented #148
diff --git a/release-notes/VERSION b/release-notes/VERSION
index 777e87f..68d2115 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -25,6 +25,9 @@
* [Issue#140]: Add 'SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN' to allow forcing
of non-scientific notation when serializing BigDecimals.
(suggested by phedny@github)
+* [Issue#148]: Add 'DeserializationFeature.FAIL_ON_INVALID_SUBTYPE`, which allows mapping
+ entries with missing or invalid type id into null references (instead of failing).
+ Also allows use of '@JsonTypeInfo.defaultImpl = NoClass.class' as alternative.
* [Issue#159]: Add more accessors in 'MappingIterator': getParser(), getParserSchema(),
readAll()
(suggested by Tom D)
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
index 53aabf1..2de1164 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
@@ -128,6 +128,19 @@
FAIL_ON_NUMBERS_FOR_ENUMS(false),
/**
+ * Feature that determines what happens when type of a polymorphic
+ * value (indicated for example by {@link com.fasterxml.jackson.annotation.JsonTypeInfo})
+ * can not be found (missing) or resolved (invalid class name, unmappable id);
+ * if enabled, an exception ir thrown; if false, null value is used instead.
+ *<p>
+ * Feature is enabled by default so that exception is thrown for missing or invalid
+ * type information.
+ *
+ * @since 2.2
+ */
+ FAIL_ON_INVALID_SUBTYPE(true),
+
+ /**
* Feature that determines whether Jackson code should catch
* and wrap {@link Exception}s (but never {@link Error}s!)
* to add additional information about
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
index bd430e4..2a0c196 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
@@ -20,7 +20,6 @@
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
-import com.fasterxml.jackson.databind.jsonschema.JsonSchema;
import com.fasterxml.jackson.databind.jsontype.*;
import com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
@@ -2542,7 +2541,8 @@
* @return Constructed JSON schema.
*/
@SuppressWarnings("deprecation")
- public JsonSchema generateJsonSchema(Class<?> t) throws JsonMappingException {
+ public com.fasterxml.jackson.databind.jsonschema.JsonSchema generateJsonSchema(Class<?> t)
+ throws JsonMappingException {
return _serializerProvider(getSerializationConfig()).generateJsonSchema(t);
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java
new file mode 100644
index 0000000..196a1c5
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NullifyingDeserializer.java
@@ -0,0 +1,55 @@
+package com.fasterxml.jackson.databind.deser.std;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+
+/**
+ * Bogus deserializer that will simply skip all content there is to map
+ * and returns Java null reference.
+ *
+ * @since 2.2
+ */
+public class NullifyingDeserializer
+ extends StdDeserializer<Object>
+{
+ private static final long serialVersionUID = 1L;
+
+ public final static NullifyingDeserializer instance = new NullifyingDeserializer();
+
+ public NullifyingDeserializer() { super(Object.class); }
+
+ /*
+ /**********************************************************
+ /* Deserializer API
+ /**********************************************************
+ */
+
+ @Override
+ public Object deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ jp.skipChildren();
+ return null;
+ }
+
+ @Override
+ public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
+ TypeDeserializer typeDeserializer)
+ throws IOException, JsonProcessingException
+ {
+ // Not sure if we need to bother but:
+
+ JsonToken t = jp.getCurrentToken();
+ switch (t) {
+ case START_ARRAY:
+ case START_OBJECT:
+ case FIELD_NAME:
+ return typeDeserializer.deserializeTypedFromAny(jp, ctxt);
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
index cab8d52..84a25a0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
@@ -119,8 +119,8 @@
throws IOException, JsonProcessingException
{
// As per [JACKSON-614], may have default implementation to use
- if (_defaultImpl != null) {
- JsonDeserializer<Object> deser = _findDefaultImplDeserializer(ctxt);
+ JsonDeserializer<Object> deser = _findDefaultImplDeserializer(ctxt);
+ if (deser != null) {
if (tb != null) {
tb.writeEndObject();
jp = tb.asParser(jp);
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java
index 76b4ac5..30a84e6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeDeserializerBase.java
@@ -8,8 +8,11 @@
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.annotation.NoClass;
+import com.fasterxml.jackson.databind.deser.std.NullifyingDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
@@ -21,7 +24,7 @@
implements java.io.Serializable
{
private static final long serialVersionUID = 278445030337366675L;
-
+
protected final TypeIdResolver _idResolver;
protected final JavaType _baseType;
@@ -178,9 +181,20 @@
protected final JsonDeserializer<Object> _findDefaultImplDeserializer(DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
+ /* 06-Feb-2013, tatu: As per [Issue#148], consider default implementation value of
+ * {@link NoClass} to mean "serialize as null"; as well as DeserializationFeature
+ * to do swift mapping to null
+ */
if (_defaultImpl == null) {
+ if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE)) {
+ return NullifyingDeserializer.instance;
+ }
return null;
}
+ if (_defaultImpl.getRawClass() == NoClass.class) {
+ return NullifyingDeserializer.instance;
+ }
+
synchronized (_defaultImpl) {
if (_defaultImplDeserializer == null) {
_defaultImplDeserializer = ctxt.findContextualValueDeserializer(
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedDeserializationWithDefault.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedDeserializationWithDefault.java
index d9d3651..f651c42 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedDeserializationWithDefault.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestTypedDeserializationWithDefault.java
@@ -4,6 +4,9 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.NoClass;
/**
* Unit tests related to [JACKSON-712]; specialized handling of
@@ -39,12 +42,20 @@
}
}
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type",
+ defaultImpl = NoClass.class)
+ public static class DefaultWithNoClass { }
+
+ // and then one with no defaultImpl nor listed subtypes
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+ abstract static class MysteryPolymorphic { }
+
/*
/**********************************************************
/* Unit tests, deserialization
/**********************************************************
*/
-
+
public void testDeserializationWithObject() throws Exception
{
Inter inter = objectReader(Inter.class).readValue("{\"type\": \"mine\", \"blah\": [\"a\", \"b\", \"c\"]}");
@@ -73,4 +84,24 @@
assertTrue(inter instanceof LegacyInter);
assertEquals(Arrays.asList("a", "b"), ((MyInter) inter).blah);
}
+
+ // [Issue#148]
+ public void testDefaultAsNoClass() throws Exception
+ {
+ Object ob = objectReader(DefaultWithNoClass.class).readValue("{ }");
+ assertNull(ob);
+ ob = objectReader(DefaultWithNoClass.class).readValue("{ \"bogus\":3 }");
+ assertNull(ob);
+ }
+
+ // [Issue#148]
+ public void testBadTypeAsNull() throws Exception
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
+ Object ob = mapper.readValue("{}", MysteryPolymorphic.class);
+ assertNull(ob);
+ ob = mapper.readValue("{ \"whatever\":13}", MysteryPolymorphic.class);
+ assertNull(ob);
+ }
}