Skip to content

Commit f2bb0df

Browse files
committed
Fix #1194
1 parent 24de8a2 commit f2bb0df

File tree

6 files changed

+192
-74
lines changed

6 files changed

+192
-74
lines changed

release-notes/VERSION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ Project: jackson-databind
55
------------------------------------------------------------------------
66

77
2.7.4 (not yet released)
8+
89
#1178: `@JsonSerialize(contentAs=superType)` behavior disallowed in 2.7
910
#1189: Converter called twice results in ClassCastException
1011
(reported by carrino@github)
1112
#1191: Non-matching quotes used in error message for date parsing
1213
#1194: Incorrect signature for generic type via `JavaType.getGenericSignature
14+
#1198: Problem with `@JsonTypeInfo.As.EXTERNAL_PROPERTY`, `defaultImpl`, missing type id, NPE
1315
- Improve handling of custom content (de)serializers for `AtomicReference`
1416

1517
2.7.3 (16-Mar-2016)

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,15 @@ protected final void _deserializeAndSet(JsonParser p, DeserializationContext ctx
257257
TokenBuffer merged = new TokenBuffer(p, ctxt);
258258
merged.writeStartArray();
259259
merged.writeString(typeId);
260-
260+
261261
merged.copyCurrentStructure(p2);
262262
merged.writeEndArray();
263263
// needs to point to START_OBJECT (or whatever first token is)
264264
JsonParser mp = merged.asParser(p);
265265
mp.nextToken();
266266
_properties[index].getProperty().deserializeAndSet(mp, ctxt, bean);
267267
}
268-
268+
269269
/*
270270
/**********************************************************
271271
/* Helper classes
@@ -284,7 +284,7 @@ public void addExternal(SettableBeanProperty property, TypeDeserializer typeDese
284284
_nameToPropertyIndex.put(property.getName(), index);
285285
_nameToPropertyIndex.put(typeDeser.getPropertyName(), index);
286286
}
287-
287+
288288
public ExternalTypeHandler build() {
289289
return new ExternalTypeHandler(_properties.toArray(new ExtTypedProperty[_properties.size()]),
290290
_nameToPropertyIndex, null, null);
@@ -296,7 +296,7 @@ private final static class ExtTypedProperty
296296
private final SettableBeanProperty _property;
297297
private final TypeDeserializer _typeDeserializer;
298298
private final String _typePropertyName;
299-
299+
300300
public ExtTypedProperty(SettableBeanProperty property, TypeDeserializer typeDeser)
301301
{
302302
_property = property;
@@ -312,16 +312,21 @@ public boolean hasDefaultType() {
312312
return _typeDeserializer.getDefaultImpl() != null;
313313
}
314314

315+
/**
316+
* Specialized called when we need to expose type id of `defaultImpl` when
317+
* serializing: we may need to expose it for assignment to a property, or
318+
* it may be requested as visible for some other reason.
319+
*/
315320
public String getDefaultTypeId() {
316321
Class<?> defaultType = _typeDeserializer.getDefaultImpl();
317322
if (defaultType == null) {
318323
return null;
319324
}
320325
return _typeDeserializer.getTypeIdResolver().idFromValueAndType(null, defaultType);
321326
}
322-
327+
323328
public String getTypePropertyName() { return _typePropertyName; }
324-
329+
325330
public SettableBeanProperty getProperty() {
326331
return _property;
327332
}

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/TypeNameIdResolver.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
11
package com.fasterxml.jackson.databind.jsontype.impl;
22

3+
import java.util.*;
4+
35
import com.fasterxml.jackson.annotation.JsonTypeInfo;
46
import com.fasterxml.jackson.databind.BeanDescription;
57
import com.fasterxml.jackson.databind.DatabindContext;
68
import com.fasterxml.jackson.databind.JavaType;
79
import com.fasterxml.jackson.databind.cfg.MapperConfig;
810
import com.fasterxml.jackson.databind.jsontype.NamedType;
911

10-
import java.util.Collection;
11-
import java.util.HashMap;
12-
import java.util.TreeSet;
13-
1412
public class TypeNameIdResolver extends TypeIdResolverBase
1513
{
1614
protected final MapperConfig<?> _config;
1715

1816
/**
1917
* Mappings from class name to type id, used for serialization
2018
*/
21-
protected final HashMap<String, String> _typeToId;
19+
protected final Map<String, String> _typeToId;
2220

2321
/**
2422
* Mappings from type id to JavaType, used for deserialization
2523
*/
26-
protected final HashMap<String, JavaType> _idToType;
24+
protected final Map<String, JavaType> _idToType;
2725

2826
protected TypeNameIdResolver(MapperConfig<?> config, JavaType baseType,
29-
HashMap<String, String> typeToId, HashMap<String, JavaType> idToType)
27+
Map<String, String> typeToId, Map<String, JavaType> idToType)
3028
{
3129
super(baseType, config.getTypeFactory());
3230
_config = config;
@@ -39,14 +37,17 @@ public static TypeNameIdResolver construct(MapperConfig<?> config, JavaType base
3937
{
4038
// sanity check
4139
if (forSer == forDeser) throw new IllegalArgumentException();
42-
HashMap<String, String> typeToId = null;
43-
HashMap<String, JavaType> idToType = null;
40+
Map<String, String> typeToId = null;
41+
Map<String, JavaType> idToType = null;
4442

4543
if (forSer) {
4644
typeToId = new HashMap<String, String>();
4745
}
4846
if (forDeser) {
4947
idToType = new HashMap<String, JavaType>();
48+
// 14-Apr-2016, tatu: Apparently needed for special case of `defaultImpl`;
49+
// see [databind#1198] for details.
50+
typeToId = new TreeMap<String, String>();
5051
}
5152
if (subtypes != null) {
5253
for (NamedType t : subtypes) {
@@ -59,10 +60,8 @@ public static TypeNameIdResolver construct(MapperConfig<?> config, JavaType base
5960
typeToId.put(cls.getName(), id);
6061
}
6162
if (forDeser) {
62-
/* 24-Feb-2011, tatu: [JACKSON-498] One more problem; sometimes
63-
* we have same name for multiple types; if so, use most specific
64-
* one.
65-
*/
63+
// One more problem; sometimes we have same name for multiple types;
64+
// if so, use most specific
6665
JavaType prev = idToType.get(id);
6766
if (prev != null) { // Can only override if more specific
6867
if (cls.isAssignableFrom(prev.getRawClass())) { // nope, more generic (or same)
@@ -87,12 +86,13 @@ public String idFromValue(Object value)
8786

8887
protected String idFromClass(Class<?> clazz)
8988
{
90-
if(clazz==null){
89+
if (clazz == null) {
9190
return null;
9291
}
9392
Class<?> cls = _typeFactory.constructType(clazz).getRawClass();
9493
final String key = cls.getName();
9594
String name;
95+
9696
synchronized (_typeToId) {
9797
name = _typeToId.get(key);
9898
if (name == null) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.fasterxml.jackson.databind.jsontype;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonSubTypes;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
7+
import com.fasterxml.jackson.databind.*;
8+
9+
public class ExternalTypeId198Test extends BaseMapTest
10+
{
11+
public enum Attacks { KICK, PUNCH }
12+
13+
static class Character {
14+
public String name;
15+
public Attacks preferredAttack;
16+
17+
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, defaultImpl=Kick.class,
18+
include=JsonTypeInfo.As.EXTERNAL_PROPERTY, property="preferredAttack")
19+
@JsonSubTypes({
20+
@JsonSubTypes.Type(value=Kick.class, name="KICK"),
21+
@JsonSubTypes.Type(value=Punch.class, name="PUNCH")
22+
})
23+
public Attack attack;
24+
}
25+
26+
public static abstract class Attack {
27+
public String side;
28+
29+
@JsonCreator
30+
public Attack(String side) {
31+
this.side = side;
32+
}
33+
}
34+
35+
public static class Kick extends Attack {
36+
@JsonCreator
37+
public Kick(String side) {
38+
super(side);
39+
}
40+
}
41+
42+
public static class Punch extends Attack {
43+
@JsonCreator
44+
public Punch(String side) {
45+
super(side);
46+
}
47+
}
48+
49+
final ObjectMapper MAPPER = new ObjectMapper();
50+
51+
public void testFails() throws Exception {
52+
String json = "{ \"name\": \"foo\", \"attack\":\"right\" } }";
53+
54+
Character character = MAPPER.readValue(json, Character.class);
55+
56+
assertNotNull(character);
57+
assertNotNull(character.attack);
58+
assertEquals("foo", character.name);
59+
}
60+
61+
public void testWorks() throws Exception {
62+
String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" } }";
63+
64+
Character character = MAPPER.readValue(json, Character.class);
65+
66+
assertNotNull(character);
67+
assertNotNull(character.attack);
68+
assertEquals("foo", character.name);
69+
}
70+
}

src/test/java/com/fasterxml/jackson/databind/jsontype/TestExternalId.java

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public ExternalBeanWithCreator(@JsonProperty("foo") int f)
7474
value = new ValueBean(f);
7575
}
7676
}
77-
77+
7878
@JsonTypeName("vbean")
7979
static class ValueBean {
8080
public int value;
@@ -251,6 +251,36 @@ static class Payload928 {
251251
public String something;
252252
}
253253

254+
enum Type965 { BIG_DECIMAL }
255+
256+
static class Wrapper965 {
257+
protected Type965 typeEnum;
258+
259+
protected Object value;
260+
261+
@JsonGetter("type")
262+
String getTypeString() {
263+
return typeEnum.name();
264+
}
265+
266+
@JsonSetter("type")
267+
void setTypeString(String type) {
268+
this.typeEnum = Type965.valueOf(type);
269+
}
270+
271+
@JsonGetter(value = "objectValue")
272+
Object getValue() {
273+
return value;
274+
}
275+
276+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
277+
@JsonSubTypes({ @JsonSubTypes.Type(name = "BIG_DECIMAL", value = BigDecimal.class) })
278+
@JsonSetter(value = "objectValue")
279+
private void setValue(Object value) {
280+
this.value = value;
281+
}
282+
}
283+
254284
/*
255285
/**********************************************************
256286
/* Unit tests, serialization
@@ -395,29 +425,7 @@ public void testIssue831() throws Exception
395425
assertEquals("dog", result.petType);
396426
}
397427

398-
// For [Issue#96]: should allow use of default impl, if property missing
399-
/* 18-Jan-2013, tatu: Unfortunately this collides with [Issue#118], and I don't
400-
* know what the best resolution is. For now at least
401-
*/
402-
/*
403-
public void testWithDefaultAndMissing() throws Exception
404-
{
405-
ExternalBeanWithDefault input = new ExternalBeanWithDefault(13);
406-
// baseline: include type, verify things work:
407-
String fullJson = MAPPER.writeValueAsString(input);
408-
ExternalBeanWithDefault output = MAPPER.readValue(fullJson, ExternalBeanWithDefault.class);
409-
assertNotNull(output);
410-
assertNotNull(output.bean);
411-
// and then try without type info...
412-
ExternalBeanWithDefault defaulted = MAPPER.readValue("{\"bean\":{\"value\":13}}",
413-
ExternalBeanWithDefault.class);
414-
assertNotNull(defaulted);
415-
assertNotNull(defaulted.bean);
416-
assertSame(ValueBean.class, defaulted.bean.getClass());
417-
}
418-
*/
419-
420-
// For [Issue#118]
428+
// For [databind#118]
421429
// Note: String works fine, since no type id will used; other scalar types have issues
422430
public void testWithScalar118() throws Exception
423431
{
@@ -431,7 +439,7 @@ public void testWithScalar118() throws Exception
431439
assertTrue(result.value instanceof java.util.Date);
432440
}
433441

434-
// For [Issue#118] using "natural" type(s)
442+
// For [databind#118] using "natural" type(s)
435443
public void testWithNaturalScalar118() throws Exception
436444
{
437445
ExternalTypeWithNonPOJO input = new ExternalTypeWithNonPOJO(Integer.valueOf(13));
@@ -511,35 +519,6 @@ public void testInverseExternalId928() throws Exception
511519
assertEquals(Payload928.class, envelope2._payload.getClass());
512520
}
513521

514-
enum Type965 { BIG_DECIMAL }
515-
516-
static class Wrapper965 {
517-
protected Type965 typeEnum;
518-
519-
protected Object value;
520-
521-
@JsonGetter("type")
522-
String getTypeString() {
523-
return typeEnum.name();
524-
}
525-
526-
@JsonSetter("type")
527-
void setTypeString(String type) {
528-
this.typeEnum = Type965.valueOf(type);
529-
}
530-
531-
@JsonGetter(value = "objectValue")
532-
Object getValue() {
533-
return value;
534-
}
535-
536-
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
537-
@JsonSubTypes({ @JsonSubTypes.Type(name = "BIG_DECIMAL", value = BigDecimal.class) })
538-
@JsonSetter(value = "objectValue")
539-
private void setValue(Object value) {
540-
this.value = value;
541-
}
542-
}
543522
// for [databind#965]
544523
public void testBigDecimal965() throws Exception
545524
{

0 commit comments

Comments
 (0)