aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java12
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java15
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java71
3 files changed, 81 insertions, 17 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java
index 226668ea0..682603d75 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java
@@ -1,5 +1,7 @@
package com.fasterxml.jackson.databind.jsontype;
+import java.util.Objects;
+
/**
* Simple container class for types with optional logical name, used
* as external identifier
@@ -14,10 +16,10 @@ public final class NamedType implements java.io.Serializable
protected String _name;
public NamedType(Class<?> c) { this(c, null); }
-
+
public NamedType(Class<?> c, String name) {
_class = c;
- _hashCode = c.getName().hashCode();
+ _hashCode = (c.getName() + (name != null ? name : "")).hashCode();
setName(name);
}
@@ -28,14 +30,16 @@ public final class NamedType implements java.io.Serializable
public boolean hasName() { return _name != null; }
/**
- * Equality is defined based on class only, not on name
+ * Equality is defined based on class and name
*/
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != getClass()) return false;
- return _class == ((NamedType) o)._class;
+ NamedType other = (NamedType)o;
+ if (!Objects.equals(_name, other._name)) return false;
+ return _class == other._class;
}
@Override
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java
index b705511f6..3d2a3dcd2 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java
@@ -112,7 +112,8 @@ public class StdSubtypeResolver
AnnotatedClass type)
{
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
- HashMap<NamedType, NamedType> subtypes = new HashMap<NamedType, NamedType>();
+ HashMap<NamedType, NamedType> subtypes = new HashMap<>();
+
// then consider registered subtypes (which have precedence over annotations)
if (_registeredSubtypes != null) {
Class<?> rawBase = type.getRawType();
@@ -223,19 +224,23 @@ public class StdSubtypeResolver
}
}
+ //For Serialization we only want to return a single NamedType per class so it's
+ //unambiguous what name we use.
+ NamedType typeOnlyNamedType = new NamedType(namedType.getType());
+
// First things first: is base type itself included?
- if (collectedSubtypes.containsKey(namedType)) {
+ if (collectedSubtypes.containsKey(typeOnlyNamedType)) {
// if so, no recursion; however, may need to update name?
if (namedType.hasName()) {
- NamedType prev = collectedSubtypes.get(namedType);
+ NamedType prev = collectedSubtypes.get(typeOnlyNamedType);
if (!prev.hasName()) {
- collectedSubtypes.put(namedType, namedType);
+ collectedSubtypes.put(typeOnlyNamedType, namedType);
}
}
return;
}
// if it wasn't, add and check subtypes recursively
- collectedSubtypes.put(namedType, namedType);
+ collectedSubtypes.put(typeOnlyNamedType, namedType);
Collection<NamedType> st = ai.findSubtypes(annotatedType);
if (st != null && !st.isEmpty()) {
for (NamedType subtype : st) {
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 0ffd4564c..c6fa2bb35 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypes.java
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.Version;
import java.util.*;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
@@ -38,7 +39,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
// "Empty" bean
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME)
static abstract class BaseBean { }
-
+
static class EmptyBean extends BaseBean { }
static class EmptyNonFinal { }
@@ -49,7 +50,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
{
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME)
public SuperType value;
-
+
public PropertyBean() { this(null); }
public PropertyBean(SuperType v) { value = v; }
}
@@ -70,6 +71,28 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
public int a;
}
+ static class Sub extends SuperTypeWithoutDefault {
+ public int a;
+
+ public Sub(){}
+ public Sub(int a) {
+ this.a = a;
+ }
+ }
+
+ static class POJOWrapper {
+ @JsonProperty
+ Sub sub1;
+ @JsonProperty
+ Sub sub2;
+
+ public POJOWrapper(){}
+ public POJOWrapper(Sub sub1, Sub sub2) {
+ this.sub1 = sub1;
+ this.sub2 = sub2;
+ }
+ }
+
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.PROPERTY, property="type")
@JsonSubTypes({ @JsonSubTypes.Type(ImplX.class),
@JsonSubTypes.Type(ImplY.class) })
@@ -118,7 +141,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
public Issue1125Wrapper() { }
public Issue1125Wrapper(Base1125 v) { value = v; }
}
-
+
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, defaultImpl=Default1125.class)
@JsonSubTypes({ @JsonSubTypes.Type(Interm1125.class) })
static class Base1125 {
@@ -204,7 +227,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
result = mapper.readValue(json, PropertyBean.class);
assertSame(SubC.class, result.value.getClass());
}
-
+
public void testSerialization() throws Exception
{
// serialization can detect type name ok without anything extra:
@@ -217,7 +240,17 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
assertEquals("{\"@type\":\"typeB\",\"b\":1}", mapper.writeValueAsString(bean));
// and default name ought to be simple class name; with context
- assertEquals("{\"@type\":\"TestSubtypes$SubD\",\"d\":0}", mapper.writeValueAsString(new SubD()));
+ assertEquals("{\"@type\":\"TestSubtypes$SubD\",\"d\":0}", mapper.writeValueAsString(new SubD()));
+ }
+
+ public void testSerializationWithDuplicateRegisteredSubtypes() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerSubtypes(new NamedType(Sub.class, "sub1"));
+ mapper.registerSubtypes(new NamedType(Sub.class, "sub2"));
+
+ // the first registered type name is used for serialization
+ Sub sub = new Sub(15);
+ assertEquals("{\"#type\":\"sub1\",\"a\":15}", mapper.writeValueAsString(sub));
}
public void testDeserializationNonNamed() throws Exception
@@ -247,6 +280,28 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
assertEquals(-4, ((SubD) bean).d);
}
+ public void testDeserializationWithDuplicateRegisteredSubtypes()
+ throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+
+ // We can register the same class with different names
+ mapper.registerSubtypes(new NamedType(Sub.class, "sub1"));
+ mapper.registerSubtypes(new NamedType(Sub.class, "sub2"));
+
+ // fields of a POJO will be deserialized correctly according to their field name
+ POJOWrapper pojoWrapper = mapper.readValue("{\"sub1\":{\"#type\":\"sub1\",\"a\":10},\"sub2\":{\"#type\":\"sub2\",\"a\":50}}", POJOWrapper.class);
+ assertEquals(10, pojoWrapper.sub1.a);
+ assertEquals(50, pojoWrapper.sub2.a);
+
+ // Instances of the same object can be deserialized with multiple names
+ SuperTypeWithoutDefault sub1 = mapper.readValue("{\"#type\":\"sub1\", \"a\":20}", SuperTypeWithoutDefault.class);
+ assertSame(Sub.class, sub1.getClass());
+ assertEquals(20, ((Sub) sub1).a);
+ SuperTypeWithoutDefault sub2 = mapper.readValue("{\"#type\":\"sub2\", \"a\":30}", SuperTypeWithoutDefault.class);
+ assertSame(Sub.class, sub2.getClass());
+ assertEquals(30, ((Sub) sub2).a);
+ }
+
// Trying to reproduce [JACKSON-366]
public void testEmptyBean() throws Exception
{
@@ -295,7 +350,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
public void testDefaultImplViaModule() throws Exception
{
final String JSON = "{\"a\":123}";
-
+
// first: without registration etc, epic fail:
try {
MAPPER.readValue(JSON, SuperTypeWithoutDefault.class);
@@ -317,7 +372,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
bean = mapper.readValue("{\"#type\":\"foobar\"}", SuperTypeWithoutDefault.class);
assertEquals(DefaultImpl505.class, bean.getClass());
assertEquals(0, ((DefaultImpl505) bean).a);
-
+
}
public void testErrorMessage() throws Exception {
@@ -361,7 +416,7 @@ public class TestSubtypes extends com.fasterxml.jackson.databind.BaseMapTest
public void testIssue1125NonDefault() throws Exception
{
String json = MAPPER.writeValueAsString(new Issue1125Wrapper(new Impl1125(1, 2, 3)));
-
+
Issue1125Wrapper result = MAPPER.readValue(json, Issue1125Wrapper.class);
assertNotNull(result.value);
assertEquals(Impl1125.class, result.value.getClass());