|
1 | 1 | package com.fasterxml.jackson.databind.jsontype.impl;
|
2 | 2 |
|
3 | 3 | 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.*; |
11 | 5 |
|
12 | 6 | import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
| 7 | + |
13 | 8 | import com.fasterxml.jackson.core.JsonParser;
|
14 | 9 | 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.*; |
20 | 12 | import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
21 | 13 | import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
22 | 14 | import com.fasterxml.jackson.databind.jsontype.NamedType;
|
23 | 15 | import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
24 | 16 | import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
|
| 17 | +import com.fasterxml.jackson.databind.util.ClassUtil; |
25 | 18 | import com.fasterxml.jackson.databind.util.TokenBuffer;
|
26 | 19 |
|
27 | 20 | /**
|
28 | 21 | * A {@link TypeDeserializer} capable of deducing polymorphic types based on the fields available. Deduction
|
29 | 22 | * is limited to the <i>names</i> of child fields (not their values or, consequently, any nested descendants).
|
30 | 23 | * Exceptions will be thrown if not enough unique information is present to select a single subtype.
|
31 | 24 | */
|
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 | + } |
82 | 39 |
|
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 | + } |
84 | 45 |
|
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 | + } |
91 | 50 |
|
| 51 | + @Override |
| 52 | + public TypeDeserializer forProperty(BeanProperty prop) { |
| 53 | + return (prop == _property) ? this : new AsDeductionTypeDeserializer(this, prop); |
92 | 54 | }
|
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; |
111 | 88 | }
|
112 | 89 |
|
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()); |
114 | 108 |
|
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); |
118 | 112 |
|
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(); |
122 | 116 |
|
123 |
| - tb.copyCurrentStructure(p); |
| 117 | + tb.copyCurrentStructure(p); |
124 | 118 |
|
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 | + } |
131 | 127 | }
|
132 |
| - } |
133 |
| - } |
134 | 128 |
|
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 | + ); |
149 | 136 | }
|
150 |
| - } |
151 | 137 |
|
| 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 | + } |
152 | 146 | }
|
0 commit comments