Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work-around for #1565: Deserialization failure with Polymorphism using JsonTypeInfo defaultImpl #1656

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.fasterxml.jackson.databind.jsontype.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.NoClass;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.jsontype.*;

/**
Expand Down Expand Up @@ -125,8 +128,13 @@ public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
|| (_defaultImpl == NoClass.class)) {
defaultImpl = config.getTypeFactory().constructType(_defaultImpl);
} else {
defaultImpl = config.getTypeFactory()
.constructSpecializedType(baseType, _defaultImpl);
if (!baseType.getRawClass().isAssignableFrom(_defaultImpl) && isBaseActuallySpecializedType(config, baseType)) {
defaultImpl = null;
}
else {
defaultImpl = config.getTypeFactory()
.constructSpecializedType(baseType, _defaultImpl);
}
}
}

Expand All @@ -149,6 +157,40 @@ public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
}

private boolean isBaseActuallySpecializedType(DeserializationConfig config, JavaType baseType) {
// 12-Jun-2017, slobo: There is a chance that we're deserializing
// a specific class part of a polymorphic hierarchy. In this case
// baseType is actually a specific type, but the _defaultImpl comes
// from the real base type. For example:
//
// B @JsonTypeInfo(defaultImpl=S2)
// /--+--\
// S1 S2
// baseType = S1
// _defaultImpl = S2
//
// To detect this scenario we'll check whether _defaultImpl and
// baseType share a common superclass or super superclass, etc.
JavaType defaultType = config.getTypeFactory().constructType(_defaultImpl);
for (JavaType current = defaultType; current != null; current = current.getSuperClass())
{
AnnotatedClass annotatedClass = AnnotatedClass.construct(current, config);
if (annotatedClass.hasAnnotation(JsonTypeInfo.class)) {
JsonTypeInfo typeInfo = annotatedClass.getAnnotation(JsonTypeInfo.class);
if (typeInfo.defaultImpl() == null || !typeInfo.defaultImpl().equals(_defaultImpl)) {
break; // ignore any classes that have a different JsonTypeInfo annotation
}
if (annotatedClass.getRawType().isAssignableFrom(baseType.getRawClass())) {
return true;
}
}
else {
break; // ignore any classes above the JsonTypeInfo annotation
}
};
return false;
}

/*
/**********************************************************
/* Construction, configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.fasterxml.jackson.databind.jsontype;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
Expand Down Expand Up @@ -362,4 +368,106 @@ public void testIssue1125WithDefault() throws Exception
assertEquals(5, impl.b);
assertEquals(9, impl.def);
}


// [databind#1565]: defaultImpl and deserializing a concrete subclass

public void testDeserializeScenario1ToDefaultImpl() throws JsonProcessingException, IOException {
ObjectMapper om = objectMapper();
om.readerFor(Scenario1.Sub2.class).readValue("{\"type\":\"sub2\"}"); // no exception should happen
}

public void testDeserializeScenario3ToDefaultImpl() throws JsonProcessingException, IOException {
ObjectMapper om = objectMapper();
om.readerFor(Scenario3.Sub2.class).readValue("{\"type\":\"sub2\"}"); // no exception should happen
}

public void testDeserializeScenario4ToDefaultImpl() throws JsonProcessingException, IOException {
ObjectMapper om = objectMapper();
om.readerFor(Scenario4.Sub2.class).readValue("{\"type\":\"sub2\"}"); // no exception should happen
}

public void testDeserializeToUnrelatedDefaultImpl() throws JsonProcessingException, IOException {
try {
ObjectMapper om = objectMapper();
om.readerFor(Scenario2.BaseWithUnrelated.class).readValue("{}");
fail("JsonProcessingException was not thrown.");
}
catch (IllegalArgumentException e) { // should this throw another type of exception?
}
}

public void testDeserializeWithWrongDefaultImplOnlyOnLowerBaseClass() throws JsonProcessingException, IOException {
try {
ObjectMapper om = objectMapper();
om.readerFor(Scenario5.Sub1.class).readValue("{\"type\":\"sub1\"}");
fail("JsonProcessingException was not thrown.");
}
catch (IllegalArgumentException e) { // should this throw another type of exception?
}
}

/**
* A base class with a subs and one of the is default.
*/
static class Scenario1 {
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub1.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class)})
static abstract class Base {}

static class Sub1 extends Base {}

static class Sub2 extends Base {}
}

/**
* A base class with an unrelated default class. This is incorrect and should throw errors.
*/
static class Scenario2 {
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Unrelated.class)
static abstract class BaseWithUnrelated {}

static class Unrelated {}
}

/**
* Multiple levels of inheritance. 2 Base classes 3 Subs. Annotations on the higher base class.
*/
static class Scenario3 {
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub1.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class), @Type(name="sub3", value=Sub3.class)})
static abstract class BaseHigh {}
static abstract class BaseLow extends BaseHigh {}
static class Sub1 extends BaseLow {}
static class Sub2 extends BaseLow {}
static class Sub3 extends BaseHigh {}
}

/**
* Multiple levels of inheritance. 2 Base classes 3 Subs. Annotations on the lower base class
*/
static class Scenario4 {
static abstract class BaseHigh {}
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub1.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class)})
static abstract class BaseLow extends BaseHigh {}
static class Sub1 extends BaseLow {}
static class Sub2 extends BaseLow {}
static class Sub3 extends BaseHigh {}
}

/**
* Multiple levels of inheritance. 2 Base classes 3 Subs. TypeInfo on both higher and lower base class with different defaultImpl.
*/
static class Scenario5 {
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub2.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class), @Type(name="sub3", value=Sub3.class)})
static abstract class BaseHigh {}
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub3.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class)})
static abstract class BaseLow extends BaseHigh {}
static class Sub1 extends BaseLow {}
static class Sub2 extends BaseLow {}
static class Sub3 extends BaseHigh {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.fasterxml.jackson.failing;

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestDeserializePolymorphicDefaultImpl1565 extends BaseMapTest {

public void testDeserializeWithSameDefaultImplOnBothBaseClasses() throws JsonProcessingException, IOException {
try {
ObjectMapper om = objectMapper();
om.readerFor(Scenario.Sub1.class).readValue("{\"type\":\"sub1\"}");
fail("JsonProcessingException was not thrown.");
}
catch (IllegalArgumentException e) { // should this throw another type of exception?
}
}

/**
* Multiple levels of inheritance. 2 Base classes 3 Subs. TypeInfo on both higher and lower base class with same defaultImpl.
*/
static class Scenario {
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub3.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class), @Type(name="sub3", value=Sub3.class)})
static abstract class BaseHigh {}
@JsonTypeInfo(include=As.PROPERTY, property="type", use=Id.NAME, defaultImpl=Sub3.class)
@JsonSubTypes({@Type(name="sub1", value=Sub1.class), @Type(name="sub2", value=Sub2.class)})
static abstract class BaseLow extends BaseHigh {}
static class Sub1 extends BaseLow {}
static class Sub2 extends BaseLow {}
static class Sub3 extends BaseHigh {}
}

}