Skip to content

Commit 10f2ce3

Browse files
committed
Fix #1998
1 parent 0f7024e commit 10f2ce3

File tree

6 files changed

+170
-2
lines changed

6 files changed

+170
-2
lines changed

release-notes/VERSION-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Project: jackson-databind
88

99
#1565: Deserialization failure with Polymorphism using JsonTypeInfo `defaultImpl`,
1010
subtype as target
11+
#1998: Removing "type" attribute with Mixin not taken in account if
12+
using ObjectMapper.copy()
13+
(reported by SBKila@github)
1114

1215
2.9.5 (26-Mar-2018)
1316

src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,26 @@ public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai,
147147
_defaultBase64 = defaultBase64;
148148
}
149149

150+
/**
151+
* Turns out we are not necessarily 100% stateless, alas, since {@link ClassIntrospector}
152+
* typically has a cache. So this method is needed for deep copy() of Mapper.
153+
*
154+
* @since 2.9.6
155+
*/
156+
public BaseSettings copy() {
157+
return new BaseSettings(_classIntrospector.copy(),
158+
_annotationIntrospector,
159+
_propertyNamingStrategy,
160+
_typeFactory,
161+
_typeResolverBuilder,
162+
_dateFormat,
163+
_handlerInstantiator,
164+
_locale,
165+
_timeZone,
166+
_defaultBase64);
167+
168+
}
169+
150170
/*
151171
/**********************************************************
152172
/* Factory methods

src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,18 @@ protected MapperConfigBase(BaseSettings base,
134134
}
135135

136136
/**
137+
* Copy constructor usually called to make a copy for use by
138+
* ObjectMapper that is copied.
139+
*
137140
* @since 2.8
138141
*/
139142
protected MapperConfigBase(MapperConfigBase<CFG,T> src,
140143
SimpleMixInResolver mixins, RootNameLookup rootNames,
141144
ConfigOverrides configOverrides)
142145
{
143-
super(src);
146+
// 18-Apr-2018, tatu: [databind#1898] need to force copying of `ClassIntrospector`
147+
// (to clear its cache) to avoid leakage
148+
super(src, src._base.copy());
144149
_mixIns = mixins;
145150
_subtypeResolver = src._subtypeResolver;
146151
_rootNames = rootNames;

src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ public BasicClassIntrospector() {
6666
// a small cache should go a long way here
6767
_cachedFCA = new LRUMap<JavaType,BasicBeanDescription>(16, 64);
6868
}
69-
69+
70+
@Override
71+
public ClassIntrospector copy() {
72+
return new BasicClassIntrospector();
73+
}
74+
7075
/*
7176
/**********************************************************
7277
/* Factory method impls

src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ public interface MixInResolver
4747

4848
protected ClassIntrospector() { }
4949

50+
/**
51+
* Method that may be needed when `copy()`ing `ObjectMapper` instances.
52+
*
53+
* @since 2.9.6
54+
*/
55+
public abstract ClassIntrospector copy();
56+
5057
/*
5158
/**********************************************************
5259
/* Public API: factory methods
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.fasterxml.jackson.databind.mixins;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.annotation.*;
6+
7+
import com.fasterxml.jackson.databind.*;
8+
9+
public class MapperMixinsCopy1998Test extends BaseMapTest
10+
{
11+
final static String FULLMODEL="{\"format\":\"1.0\",\"child\":{\"type\":\"CHILD_B\",\"name\":\"testB\"},\"notVisible\":\"should not be present\"}";
12+
final static String EXPECTED="{\"format\":\"1.0\",\"child\":{\"name\":\"testB\"}}";
13+
14+
static class MyModelView { }
15+
16+
interface MixinConfig {
17+
interface MyModelRoot {
18+
@JsonView(MyModelView.class)
19+
public String getFormat();
20+
21+
@JsonView(MyModelView.class)
22+
public MyModelChildBase getChild();
23+
}
24+
25+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.EXISTING_PROPERTY)
26+
interface MyModelChildBase {
27+
@JsonView(MyModelView.class)
28+
public String getName();
29+
}
30+
31+
}
32+
33+
@JsonPropertyOrder({ "format", "child" })
34+
static class MyModelRoot {
35+
@JsonProperty
36+
private String format = "1.0";
37+
38+
public String getFormat() {
39+
return format;
40+
}
41+
@JsonProperty
42+
private MyModelChildBase child;
43+
44+
public MyModelChildBase getChild() {
45+
return child;
46+
}
47+
48+
public void setChild(MyModelChildBase child) {
49+
this.child = child;
50+
}
51+
52+
@JsonProperty
53+
private String notVisible = "should not be present";
54+
55+
public String getNotVisible() {
56+
return notVisible;
57+
}
58+
}
59+
60+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
61+
@JsonSubTypes({
62+
@JsonSubTypes.Type(value = MyChildA.class, name = "CHILD_A"),
63+
@JsonSubTypes.Type(value = MyChildB.class, name = "CHILD_B")
64+
})
65+
abstract static class MyModelChildBase {
66+
@JsonProperty
67+
private String name;
68+
69+
public String getName() {
70+
return name;
71+
}
72+
73+
@JsonIgnore
74+
public void setName(String name) {
75+
this.name = name;
76+
}
77+
}
78+
79+
static class MyChildA extends MyModelChildBase {
80+
public MyChildA(String name) {
81+
setName(name);
82+
}
83+
}
84+
85+
static class MyChildB extends MyModelChildBase {
86+
public MyChildB(String name) {
87+
setName(name);
88+
}
89+
}
90+
91+
public void testB_KO() throws Exception
92+
{
93+
final ObjectMapper DEFAULT = defaultMapper();
94+
MyModelRoot myModelInstance = new MyModelRoot();
95+
myModelInstance.setChild(new MyChildB("testB"));
96+
97+
ObjectMapper myObjectMapper = DEFAULT.copy();
98+
99+
String postResult = getString(myModelInstance, myObjectMapper);
100+
assertEquals(FULLMODEL, postResult);
101+
// System.out.println("postResult: "+postResult);
102+
103+
myObjectMapper = DEFAULT.copy();
104+
// myObjectMapper = defaultMapper();
105+
myObjectMapper.addMixIn(MyModelRoot.class, MixinConfig.MyModelRoot.class)
106+
.addMixIn(MyModelChildBase.class, MixinConfig.MyModelChildBase.class)
107+
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
108+
.setConfig(myObjectMapper.getSerializationConfig().withView(MyModelView.class));
109+
110+
String result = getString(myModelInstance, myObjectMapper);
111+
System.out.println("result: "+result);
112+
assertEquals(EXPECTED, result);
113+
114+
}
115+
116+
private String getString(MyModelRoot myModelInstance, ObjectMapper myObjectMapper) throws IOException {
117+
return myObjectMapper.writerFor(MyModelRoot.class).writeValueAsString(myModelInstance);
118+
}
119+
120+
private ObjectMapper defaultMapper()
121+
{
122+
return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
123+
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
124+
.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false)
125+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
126+
;
127+
}
128+
}

0 commit comments

Comments
 (0)