Improve error message for unresolved subtype ids.
diff --git a/release-notes/VERSION b/release-notes/VERSION
index 49e7205..1b70c41 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -72,6 +72,7 @@
that benefit from knowing number of elements in arrays (and would otherwise
need to buffer values to know length)
- 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.5 (not yet released)
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
index 866be42..9b9e069 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
@@ -876,15 +876,28 @@
* Helper method for constructing exception to indicate that given
* type id (parsed from JSON) could not be converted to a Java type.
*/
+ @Deprecated // since 2.5, use overloaded variant
public JsonMappingException unknownTypeException(JavaType type, String id) {
return JsonMappingException.from(_parser, "Could not resolve type id '"+id+"' into a subtype of "+type);
}
+ /**
+ * @since 2.5
+ */
+ public JsonMappingException unknownTypeException(JavaType type, String id,
+ String extraDesc) {
+ String msg = "Could not resolve type id '"+id+"' into a subtype of "+type;
+ if (extraDesc != null) {
+ msg = msg + ": "+extraDesc;
+ }
+ return JsonMappingException.from(_parser, msg);
+ }
+
public JsonMappingException endOfInputException(Class<?> instClass) {
return JsonMappingException.from(_parser, "Unexpected end-of-input when trying to deserialize a "
+instClass.getName());
}
-
+
/*
/**********************************************************
/* Overridable internal methods
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java
index ea7d1bf..e35704e 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/ClassNameIdResolver.java
@@ -139,4 +139,9 @@
}
return str;
}
+
+ @Override
+ public String getDescForKnownTypeIds() {
+ return "class name used as type id";
+ }
}
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 4a4922e..890416f 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
@@ -159,7 +159,7 @@
// As per [JACKSON-614], use the default impl if no type id available:
deser = _findDefaultImplDeserializer(ctxt);
if (deser == null) {
- throw ctxt.unknownTypeException(_baseType, typeId);
+ deser = _handleUnknownTypeId(ctxt, typeId, _idResolver, _baseType);
}
} else {
/* 16-Dec-2010, tatu: Since nominal type we get here has no (generic) type parameters,
@@ -243,4 +243,35 @@
}
return deser.deserialize(jp, ctxt);
}
+
+ /**
+ * Helper method called when given type id can not be resolved into
+ * concrete deserializer either directly (using given {@link TypeIdResolver}),
+ * or using default type.
+ * Default implementation simply throws a {@link JsonMappingException} to
+ * indicate the problem; sub-classes may choose
+ *
+ * @return If it is possible to resolve type id into a {@link JsonDeserializer}
+ * should return that deserializer; otherwise throw an exception to indicate
+ * the problem.
+ *
+ * @since 2.5
+ */
+ protected JsonDeserializer<Object> _handleUnknownTypeId(DeserializationContext ctxt, String typeId,
+ TypeIdResolver idResolver, JavaType baseType)
+ throws IOException
+ {
+ String extraDesc;
+ if (idResolver instanceof TypeIdResolverBase) {
+ extraDesc = ((TypeIdResolverBase) idResolver).getDescForKnownTypeIds();
+ if (extraDesc == null) {
+ extraDesc = "known type ids are not statically known";
+ } else {
+ extraDesc = "known type ids = " + extraDesc;
+ }
+ } else {
+ extraDesc = null;
+ }
+ throw ctxt.unknownTypeException(_baseType, typeId, extraDesc);
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeIdResolverBase.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeIdResolverBase.java
index 771283a..bea6d49 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeIdResolverBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeIdResolverBase.java
@@ -71,4 +71,14 @@
public JavaType typeFromId(DatabindContext context, String id) {
return typeFromId(id);
}
+
+ /**
+ * Helper method used to get a simple description of all known type ids,
+ * for use in error messages.
+ *<p>
+ * TODO: demote down to be part of {@link TypeIdResolver} in 2.6 or 2.7
+ */
+ public String getDescForKnownTypeIds() {
+ return null;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java
index ddb7174..1622496 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java
@@ -132,7 +132,12 @@
*/
return _idToType.get(id);
}
-
+
+ @Override
+ public String getDescForKnownTypeIds() {
+ return new TreeSet<String>(_idToType.keySet()).toString();
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/src/test/java/com/fasterxml/jackson/databind/TestHandlerInstantiation.java b/src/test/java/com/fasterxml/jackson/databind/TestHandlerInstantiation.java
index afb3ebf..1422ced 100644
--- a/src/test/java/com/fasterxml/jackson/databind/TestHandlerInstantiation.java
+++ b/src/test/java/com/fasterxml/jackson/databind/TestHandlerInstantiation.java
@@ -120,11 +120,12 @@
public CustomIdResolver(String idForBean) {
_id = idForBean;
}
-
+
@Override
public Id getMechanism() {
return Id.CUSTOM;
}
+
@Override
public String idFromValue(Object value)
{
@@ -133,16 +134,19 @@
}
return "unknown";
}
+
@Override
public String idFromValueAndType(Object value, Class<?> type) {
return idFromValue(value);
}
+
@Override
public void init(JavaType baseType) {
if (initTypes != null) {
initTypes.add(baseType);
}
}
+
@Override
public JavaType typeFromId(DatabindContext context, String id)
{
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
index 29f83a5..68df8f4 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
@@ -65,7 +66,22 @@
static class DefaultImpl505 extends SuperTypeWithoutDefault {
public int a;
}
-
+
+ @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.PROPERTY, property="type")
+ @JsonSubTypes({ @JsonSubTypes.Type(ImplX.class),
+ @JsonSubTypes.Type(ImplY.class) })
+ static abstract class BaseX { }
+
+ @JsonTypeName("x")
+ static class ImplX extends BaseX {
+ public int x;
+ }
+
+ @JsonTypeName("y")
+ static class ImplY extends BaseX {
+ public int y;
+ }
+
/*
/**********************************************************
/* Unit tests
@@ -211,5 +227,14 @@
assertEquals(0, ((DefaultImpl505) bean).a);
}
-}
+ public void testErrorMessage() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ mapper.readValue("{ \"type\": \"z\"}", BaseX.class);
+ fail("Should have failed");
+ } catch (JsonMappingException e) {
+ verifyException(e, "known type ids =");
+ }
+ }
+}