Skip to content

Commit 33dbd37

Browse files
Observe the FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY when using creator properties
The feature was only getting examined in the "normal" case, and not when using creator properties Fixes #2404
1 parent 92f8e5d commit 33dbd37

File tree

2 files changed

+256
-4
lines changed

2 files changed

+256
-4
lines changed

src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,16 @@ public Object complete(JsonParser p, DeserializationContext ctxt,
278278
}
279279
} else if (_tokens[i] == null) {
280280
SettableBeanProperty prop = extProp.getProperty();
281-
ctxt.reportInputMismatch(_beanType,
282-
"Missing property '%s' for external type id '%s'",
283-
prop.getName(), _properties[i].getTypePropertyName());
281+
if (prop.isRequired() ||
282+
ctxt.isEnabled(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY)) {
283+
ctxt.reportInputMismatch(_beanType,
284+
"Missing property '%s' for external type id '%s'",
285+
prop.getName(), _properties[i].getTypePropertyName());
286+
}
287+
}
288+
if (_tokens[i] != null) {
289+
values[i] = _deserialize(p, ctxt, i, typeId);
284290
}
285-
values[i] = _deserialize(p, ctxt, i, typeId);
286291

287292
final SettableBeanProperty prop = extProp.getProperty();
288293
// also: if it's creator prop, fill in
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package com.fasterxml.jackson.databind.jsontype.ext;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonSubTypes;
6+
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
7+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
8+
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
9+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
10+
import com.fasterxml.jackson.databind.DeserializationFeature;
11+
import com.fasterxml.jackson.databind.JsonMappingException;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
14+
import org.junit.Rule;
15+
import org.junit.Test;
16+
import org.junit.rules.ExpectedException;
17+
18+
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertNull;
20+
import static org.junit.Assert.assertSame;
21+
22+
// for [databind#2404]
23+
public class TestPropertyCreatorSubtypesExternalPropertyMissingProperty
24+
{
25+
@Rule
26+
public ExpectedException thrown = ExpectedException.none();
27+
28+
/**
29+
* Base class - external property for Fruit subclasses.
30+
*/
31+
static class Box {
32+
private String type;
33+
@JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type")
34+
@JsonSubTypes({
35+
@Type(value = Apple.class, name = "apple"),
36+
@Type(value = Orange.class, name = "orange")
37+
})
38+
private Fruit fruit;
39+
40+
private Box(String type, Fruit fruit) {
41+
this.type = type;
42+
this.fruit = fruit;
43+
}
44+
45+
@JsonCreator
46+
public static Box getBox(@JsonProperty("type") String type, @JsonProperty("fruit") Fruit fruit) {
47+
return new Box(type, fruit);
48+
}
49+
50+
public String getType() {
51+
return type;
52+
}
53+
54+
public Fruit getFruit() {
55+
return fruit;
56+
}
57+
}
58+
59+
static abstract class Fruit {
60+
private String name;
61+
62+
protected Fruit(String n) {
63+
name = n;
64+
}
65+
66+
public String getName() {
67+
return name;
68+
}
69+
}
70+
71+
static class Apple extends Fruit {
72+
private int seedCount;
73+
74+
private Apple(String name, int b) {
75+
super(name);
76+
seedCount = b;
77+
}
78+
79+
public int getSeedCount() {
80+
return seedCount;
81+
}
82+
83+
@JsonCreator
84+
public static Apple getApple(@JsonProperty("name") String name, @JsonProperty("seedCount") int seedCount) {
85+
return new Apple(name, seedCount);
86+
}
87+
}
88+
89+
static class Orange extends Fruit {
90+
private String color;
91+
private Orange(String name, String c) {
92+
super(name);
93+
color = c;
94+
}
95+
96+
public String getColor() {
97+
return color;
98+
}
99+
100+
@JsonCreator
101+
public static Orange getOrange(@JsonProperty("name") String name, @JsonProperty("color") String color) {
102+
return new Orange(name, color);
103+
}
104+
}
105+
106+
private final ObjectMapper MAPPER = new ObjectMapper();
107+
108+
/*
109+
/**********************************************************
110+
/* Mock data
111+
/**********************************************************
112+
*/
113+
114+
private static final Orange orange = new Orange("Orange", "orange");
115+
private static final Box orangeBox = new Box("orange", orange);
116+
private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}";
117+
private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}";
118+
private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}";
119+
private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}";
120+
121+
private static final Apple apple = new Apple("Apple", 16);
122+
private static Box appleBox = new Box("apple", apple);
123+
private static final String appleBoxJson = "{\"type\":\"apple\",\"fruit\":{\"name\":\"Apple\",\"seedCount\":16}}";
124+
private static final String appleBoxNullJson = "{\"type\":\"apple\",\"fruit\":null}";
125+
private static final String appleBoxEmptyJson = "{\"type\":\"apple\",\"fruit\":{}}";
126+
private static final String appleBoxMissingJson = "{\"type\":\"apple\"}";
127+
128+
/*
129+
/**********************************************************
130+
/* Unit tests
131+
/**********************************************************
132+
*/
133+
134+
/**
135+
* Deserialization tests for external type id property present
136+
*/
137+
@Test
138+
public void testDeserializationPresent() throws Exception {
139+
MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
140+
checkOrangeBox();
141+
checkAppleBox();
142+
143+
MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
144+
checkOrangeBox();
145+
checkAppleBox();
146+
}
147+
148+
/**
149+
* Deserialization tests for external type id property null
150+
*/
151+
@Test
152+
public void testDeserializationNull() throws Exception {
153+
MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
154+
checkOrangeBoxNull(orangeBoxNullJson);
155+
checkAppleBoxNull(appleBoxNullJson);
156+
157+
MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
158+
checkOrangeBoxNull(orangeBoxNullJson);
159+
checkAppleBoxNull(appleBoxNullJson);
160+
}
161+
162+
/**
163+
* Deserialization tests for external type id property empty
164+
*/
165+
@Test
166+
public void testDeserializationEmpty() throws Exception {
167+
MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
168+
checkOrangeBoxEmpty(orangeBoxEmptyJson);
169+
checkAppleBoxEmpty(appleBoxEmptyJson);
170+
171+
MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
172+
checkOrangeBoxEmpty(orangeBoxEmptyJson);
173+
checkAppleBoxEmpty(appleBoxEmptyJson);
174+
}
175+
176+
/**
177+
* Deserialization tests for external type id property missing
178+
*/
179+
@Test
180+
public void testDeserializationMissing() throws Exception {
181+
MAPPER.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
182+
checkOrangeBoxNull(orangeBoxMissingJson);
183+
checkAppleBoxNull(appleBoxMissingJson);
184+
185+
MAPPER.enable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
186+
checkBoxJsonMappingException(orangeBoxMissingJson);
187+
checkBoxJsonMappingException(appleBoxMissingJson);
188+
}
189+
190+
private void checkOrangeBox() throws Exception {
191+
Box deserOrangeBox = MAPPER.readValue(orangeBoxJson, Box.class);
192+
assertEquals(orangeBox.getType(), deserOrangeBox.getType());
193+
194+
Fruit deserOrange = deserOrangeBox.getFruit();
195+
assertSame(Orange.class, deserOrange.getClass());
196+
assertEquals(orange.getName(), deserOrange.getName());
197+
assertEquals(orange.getColor(), ((Orange) deserOrange).getColor());
198+
}
199+
200+
private void checkAppleBox() throws Exception {
201+
Box deserAppleBox = MAPPER.readValue(appleBoxJson, Box.class);
202+
assertEquals(appleBox.getType(), deserAppleBox.getType());
203+
204+
Fruit deserApple = deserAppleBox.fruit;
205+
assertSame(Apple.class, deserApple.getClass());
206+
assertEquals(apple.getName(), deserApple.getName());
207+
assertEquals(apple.getSeedCount(), ((Apple) deserApple).getSeedCount());
208+
}
209+
210+
private void checkOrangeBoxEmpty(String json) throws Exception {
211+
Box deserOrangeBox = MAPPER.readValue(json, Box.class);
212+
assertEquals(orangeBox.getType(), deserOrangeBox.getType());
213+
214+
Fruit deserOrange = deserOrangeBox.getFruit();
215+
assertSame(Orange.class, deserOrange.getClass());
216+
assertNull(deserOrange.getName());
217+
assertNull(((Orange) deserOrange).getColor());
218+
}
219+
220+
private void checkAppleBoxEmpty(String json) throws Exception {
221+
Box deserAppleBox = MAPPER.readValue(json, Box.class);
222+
assertEquals(appleBox.getType(), deserAppleBox.getType());
223+
224+
Fruit deserApple = deserAppleBox.fruit;
225+
assertSame(Apple.class, deserApple.getClass());
226+
assertNull(deserApple.getName());
227+
assertEquals(0, ((Apple) deserApple).getSeedCount());
228+
}
229+
230+
private void checkOrangeBoxNull(String json) throws Exception {
231+
Box deserOrangeBox = MAPPER.readValue(json, Box.class);
232+
assertEquals(orangeBox.getType(), deserOrangeBox.getType());
233+
assertNull(deserOrangeBox.getFruit());
234+
}
235+
236+
private void checkAppleBoxNull(String json) throws Exception {
237+
Box deserAppleBox = MAPPER.readValue(json, Box.class);
238+
assertEquals(appleBox.getType(), deserAppleBox.getType());
239+
assertNull(deserAppleBox.getFruit());
240+
}
241+
242+
private void checkBoxJsonMappingException(String json) throws Exception {
243+
thrown.expect(JsonMappingException.class);
244+
thrown.expectMessage("Missing property 'fruit' for external type id 'type'");
245+
MAPPER.readValue(json, Box.class);
246+
}
247+
}

0 commit comments

Comments
 (0)