Skip to content

Commit 0a1ba4b

Browse files
committed
Update release notes wrt #43, minor cleanup
1 parent 9a271ef commit 0a1ba4b

File tree

5 files changed

+141
-138
lines changed

5 files changed

+141
-138
lines changed

release-notes/CREDITS-2.x

+5
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,11 @@ Daniel Hrabovcak (TheSpiritXIII@github)
11471147
* Reported #2796: `TypeFactory.constructType()` does not take `TypeBindings` correctly
11481148
(2.11.2)
11491149
1150+
Mark Carter (drekbour@github)
1151+
* Contributed #43 implementation: Add option to resolve type from multiple existing properties,
1152+
`@JsonTypeInfo(use=DEDUCTION)`
1153+
(2.12.0)
1154+
11501155
Mike Gilbode (gilbode@github)
11511156
* Reported #792: Deserialization Not Working Right with Generic Types and Builders
11521157
(2.12.0)

release-notes/VERSION-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Project: jackson-databind
66

77
2.12.0 (not yet released)
88

9+
#43: Add option to resolve type from multiple existing properties,
10+
`@JsonTypeInfo(use=DEDUCTION)`
11+
(contributed by drekbour@github)
912
#426: `@JsonIgnoreProperties` does not prevent Exception Conflicting getter/setter
1013
definitions for property
1114
(reported by gmkll@github)
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,146 @@
11
package com.fasterxml.jackson.databind.jsontype.impl;
22

33
import java.io.IOException;
4-
import java.util.BitSet;
5-
import java.util.Collection;
6-
import java.util.HashMap;
7-
import java.util.Iterator;
8-
import java.util.LinkedList;
9-
import java.util.List;
10-
import java.util.Map;
4+
import java.util.*;
115

126
import com.fasterxml.jackson.annotation.JsonTypeInfo;
7+
138
import com.fasterxml.jackson.core.JsonParser;
149
import com.fasterxml.jackson.core.JsonToken;
15-
import com.fasterxml.jackson.databind.BeanProperty;
16-
import com.fasterxml.jackson.databind.DeserializationConfig;
17-
import com.fasterxml.jackson.databind.DeserializationContext;
18-
import com.fasterxml.jackson.databind.JavaType;
19-
import com.fasterxml.jackson.databind.MapperFeature;
10+
11+
import com.fasterxml.jackson.databind.*;
2012
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
2113
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
2214
import com.fasterxml.jackson.databind.jsontype.NamedType;
2315
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
2416
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
17+
import com.fasterxml.jackson.databind.util.ClassUtil;
2518
import com.fasterxml.jackson.databind.util.TokenBuffer;
2619

2720
/**
2821
* A {@link TypeDeserializer} capable of deducing polymorphic types based on the fields available. Deduction
2922
* is limited to the <i>names</i> of child fields (not their values or, consequently, any nested descendants).
3023
* Exceptions will be thrown if not enough unique information is present to select a single subtype.
3124
*/
32-
public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer {
33-
34-
// Fieldname -> bitmap-index of every field discovered, across all subtypes
35-
private final Map<String, Integer> fieldBitIndex;
36-
// Bitmap of available fields in each subtype (including its parents)
37-
private final Map<BitSet, String> subtypeFingerprints;
38-
39-
public AsDeductionTypeDeserializer(JavaType bt, TypeIdResolver idRes, JavaType defaultImpl, DeserializationConfig config, Collection<NamedType> subtypes) {
40-
super(bt, idRes, null, false, defaultImpl);
41-
fieldBitIndex = new HashMap<>();
42-
subtypeFingerprints = buildFingerprints(config, subtypes);
43-
}
44-
45-
public AsDeductionTypeDeserializer(AsDeductionTypeDeserializer src, BeanProperty property) {
46-
super(src, property);
47-
fieldBitIndex = src.fieldBitIndex;
48-
subtypeFingerprints = src.subtypeFingerprints;
49-
}
50-
51-
@Override
52-
public JsonTypeInfo.As getTypeInclusion() {
53-
return null;
54-
}
55-
56-
@Override
57-
public TypeDeserializer forProperty(BeanProperty prop) {
58-
return (prop == _property) ? this : new AsDeductionTypeDeserializer(this, prop);
59-
}
60-
61-
protected Map<BitSet, String> buildFingerprints(DeserializationConfig config, Collection<NamedType> subtypes) {
62-
boolean ignoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
63-
64-
int nextField = 0;
65-
Map<BitSet, String> fingerprints = new HashMap<>();
66-
67-
for (NamedType subtype : subtypes) {
68-
JavaType subtyped = config.getTypeFactory().constructType(subtype.getType());
69-
List<BeanPropertyDefinition> properties = config.introspect(subtyped).findProperties();
70-
71-
BitSet fingerprint = new BitSet(nextField + properties.size());
72-
for (BeanPropertyDefinition property : properties) {
73-
String name = property.getName();
74-
if (ignoreCase) name = name.toLowerCase();
75-
Integer bitIndex = fieldBitIndex.get(name);
76-
if (bitIndex == null) {
77-
bitIndex = nextField;
78-
fieldBitIndex.put(name, nextField++);
79-
}
80-
fingerprint.set(bitIndex);
81-
}
25+
public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer
26+
{
27+
private static final long serialVersionUID = 1L;
28+
29+
// Fieldname -> bitmap-index of every field discovered, across all subtypes
30+
private final Map<String, Integer> fieldBitIndex;
31+
// Bitmap of available fields in each subtype (including its parents)
32+
private final Map<BitSet, String> subtypeFingerprints;
33+
34+
public AsDeductionTypeDeserializer(JavaType bt, TypeIdResolver idRes, JavaType defaultImpl, DeserializationConfig config, Collection<NamedType> subtypes) {
35+
super(bt, idRes, null, false, defaultImpl);
36+
fieldBitIndex = new HashMap<>();
37+
subtypeFingerprints = buildFingerprints(config, subtypes);
38+
}
8239

83-
String existingFingerprint = fingerprints.put(fingerprint, subtype.getType().getName());
40+
public AsDeductionTypeDeserializer(AsDeductionTypeDeserializer src, BeanProperty property) {
41+
super(src, property);
42+
fieldBitIndex = src.fieldBitIndex;
43+
subtypeFingerprints = src.subtypeFingerprints;
44+
}
8445

85-
// Validate uniqueness
86-
if (existingFingerprint != null) {
87-
throw new IllegalStateException(
88-
String.format("Subtypes %s and %s have the same signature and cannot be uniquely deduced.", existingFingerprint, subtype.getType().getName())
89-
);
90-
}
46+
@Override
47+
public JsonTypeInfo.As getTypeInclusion() {
48+
return null;
49+
}
9150

51+
@Override
52+
public TypeDeserializer forProperty(BeanProperty prop) {
53+
return (prop == _property) ? this : new AsDeductionTypeDeserializer(this, prop);
9254
}
93-
return fingerprints;
94-
}
95-
96-
@Override
97-
public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
98-
99-
JsonToken t = p.currentToken();
100-
if (t == JsonToken.START_OBJECT) {
101-
t = p.nextToken();
102-
} else {
103-
/* This is most likely due to the fact that not all Java types are
104-
* serialized as JSON Objects; so if "as-property" inclusion is requested,
105-
* serialization of things like Lists must be instead handled as if
106-
* "as-wrapper-array" was requested.
107-
* But this can also be due to some custom handling: so, if "defaultImpl"
108-
* is defined, it will be asked to handle this case.
109-
*/
110-
return _deserializeTypedUsingDefaultImpl(p, ctxt, null);
55+
56+
protected Map<BitSet, String> buildFingerprints(DeserializationConfig config, Collection<NamedType> subtypes) {
57+
boolean ignoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
58+
59+
int nextField = 0;
60+
Map<BitSet, String> fingerprints = new HashMap<>();
61+
62+
for (NamedType subtype : subtypes) {
63+
JavaType subtyped = config.getTypeFactory().constructType(subtype.getType());
64+
List<BeanPropertyDefinition> properties = config.introspect(subtyped).findProperties();
65+
66+
BitSet fingerprint = new BitSet(nextField + properties.size());
67+
for (BeanPropertyDefinition property : properties) {
68+
String name = property.getName();
69+
if (ignoreCase) name = name.toLowerCase();
70+
Integer bitIndex = fieldBitIndex.get(name);
71+
if (bitIndex == null) {
72+
bitIndex = nextField;
73+
fieldBitIndex.put(name, nextField++);
74+
}
75+
fingerprint.set(bitIndex);
76+
}
77+
78+
String existingFingerprint = fingerprints.put(fingerprint, subtype.getType().getName());
79+
80+
// Validate uniqueness
81+
if (existingFingerprint != null) {
82+
throw new IllegalStateException(
83+
String.format("Subtypes %s and %s have the same signature and cannot be uniquely deduced.", existingFingerprint, subtype.getType().getName())
84+
);
85+
}
86+
}
87+
return fingerprints;
11188
}
11289

113-
List<BitSet> candidates = new LinkedList<>(subtypeFingerprints.keySet());
90+
@Override
91+
public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
92+
93+
JsonToken t = p.currentToken();
94+
if (t == JsonToken.START_OBJECT) {
95+
t = p.nextToken();
96+
} else {
97+
/* This is most likely due to the fact that not all Java types are
98+
* serialized as JSON Objects; so if "as-property" inclusion is requested,
99+
* serialization of things like Lists must be instead handled as if
100+
* "as-wrapper-array" was requested.
101+
* But this can also be due to some custom handling: so, if "defaultImpl"
102+
* is defined, it will be asked to handle this case.
103+
*/
104+
return _deserializeTypedUsingDefaultImpl(p, ctxt, null);
105+
}
106+
107+
List<BitSet> candidates = new LinkedList<>(subtypeFingerprints.keySet());
114108

115-
// Record processed tokens as we must rewind once after deducing the deserializer to use
116-
TokenBuffer tb = new TokenBuffer(p, ctxt);
117-
boolean ignoreCase = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
109+
// Record processed tokens as we must rewind once after deducing the deserializer to use
110+
TokenBuffer tb = new TokenBuffer(p, ctxt);
111+
boolean ignoreCase = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
118112

119-
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
120-
String name = p.getCurrentName();
121-
if (ignoreCase) name = name.toLowerCase();
113+
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
114+
String name = p.currentName();
115+
if (ignoreCase) name = name.toLowerCase();
122116

123-
tb.copyCurrentStructure(p);
117+
tb.copyCurrentStructure(p);
124118

125-
Integer bit = fieldBitIndex.get(name);
126-
if (bit != null) {
127-
// field is known by at least one subtype
128-
prune(candidates, bit);
129-
if (candidates.size() == 1) {
130-
return _deserializeTypedForId(p, ctxt, tb, subtypeFingerprints.get(candidates.get(0)));
119+
Integer bit = fieldBitIndex.get(name);
120+
if (bit != null) {
121+
// field is known by at least one subtype
122+
prune(candidates, bit);
123+
if (candidates.size() == 1) {
124+
return _deserializeTypedForId(p, ctxt, tb, subtypeFingerprints.get(candidates.get(0)));
125+
}
126+
}
131127
}
132-
}
133-
}
134128

135-
throw new InvalidTypeIdException(
136-
p,
137-
String.format("Cannot deduce unique subtype of %s (%d candidates match)", _baseType.toString(), candidates.size()),
138-
_baseType
139-
, "DEDUCED"
140-
);
141-
}
142-
143-
// Keep only fingerprints containing this field
144-
private static void prune(List<BitSet> candidates, int bit) {
145-
for (Iterator<BitSet> iter = candidates.iterator(); iter.hasNext(); ) {
146-
if (!iter.next().get(bit)) {
147-
iter.remove();
148-
}
129+
throw new InvalidTypeIdException(p,
130+
String.format("Cannot deduce unique subtype of %s (%d candidates match)",
131+
ClassUtil.getTypeDescription(_baseType),
132+
candidates.size()),
133+
_baseType
134+
, "DEDUCED"
135+
);
149136
}
150-
}
151137

138+
// Keep only fingerprints containing this field
139+
private static void prune(List<BitSet> candidates, int bit) {
140+
for (Iterator<BitSet> iter = candidates.iterator(); iter.hasNext(); ) {
141+
if (!iter.next().get(bit)) {
142+
iter.remove();
143+
}
144+
}
145+
}
152146
}

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

+5-10
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,14 @@
33
import java.util.Collection;
44

55
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
67
import com.fasterxml.jackson.databind.DeserializationConfig;
78
import com.fasterxml.jackson.databind.JavaType;
89
import com.fasterxml.jackson.databind.MapperFeature;
910
import com.fasterxml.jackson.databind.SerializationConfig;
1011
import com.fasterxml.jackson.databind.annotation.NoClass;
1112
import com.fasterxml.jackson.databind.cfg.MapperConfig;
12-
import com.fasterxml.jackson.databind.jsontype.NamedType;
13-
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
14-
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity;
15-
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
16-
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
17-
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
18-
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
13+
import com.fasterxml.jackson.databind.jsontype.*;
1914
import com.fasterxml.jackson.databind.util.ClassUtil;
2015

2116
/**
@@ -332,13 +327,13 @@ protected PolymorphicTypeValidator verifyBaseTypeValidity(MapperConfig<?> config
332327
{
333328
final PolymorphicTypeValidator ptv = subTypeValidator(config);
334329
if (_idType == JsonTypeInfo.Id.CLASS || _idType == JsonTypeInfo.Id.MINIMAL_CLASS) {
335-
final Validity validity = ptv.validateBaseType(config, baseType);
330+
final PolymorphicTypeValidator.Validity validity = ptv.validateBaseType(config, baseType);
336331
// If no subtypes are legal (that is, base type itself is invalid), indicate problem
337-
if (validity == Validity.DENIED) {
332+
if (validity == PolymorphicTypeValidator.Validity.DENIED) {
338333
return reportInvalidBaseType(config, baseType, ptv);
339334
}
340335
// If there's indication that any and all subtypes are fine, replace validator itself:
341-
if (validity == Validity.ALLOWED) {
336+
if (validity == PolymorphicTypeValidator.Validity.ALLOWED) {
342337
return LaissezFaireSubTypeValidator.instance;
343338
}
344339
// otherwise just return validator, is to be called for each distinct type

0 commit comments

Comments
 (0)