View Javadoc
1   package top.infra.jackson2.deser;
2   
3   import com.fasterxml.jackson.core.JsonParser;
4   import com.fasterxml.jackson.core.JsonProcessingException;
5   import com.fasterxml.jackson.core.JsonToken;
6   import com.fasterxml.jackson.databind.BeanProperty;
7   import com.fasterxml.jackson.databind.DeserializationContext;
8   import com.fasterxml.jackson.databind.JavaType;
9   import com.fasterxml.jackson.databind.JsonDeserializer;
10  import com.fasterxml.jackson.databind.JsonMappingException;
11  import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
12  import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
13  import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
14  
15  import org.apache.commons.lang3.tuple.ImmutablePair;
16  import org.apache.commons.lang3.tuple.Pair;
17  
18  import java.io.IOException;
19  import java.lang.reflect.InvocationTargetException;
20  
21  /**
22   * see: {@link com.fasterxml.jackson.databind.deser.std.MapEntryDeserializer}.
23   */
24  public class PairDeserializer extends StdDeserializer<Pair<Object, Object>> implements ContextualDeserializer {
25  
26      private static final long serialVersionUID = 1;
27  
28      // // Configuration: typing, deserializers
29  
30      protected final JavaType _type;
31  
32      /**
33       * Key deserializer to use; either passed via constructor
34       * (when indicated by annotations), or resolved when
35       * {@link #createContextual} is called;
36       */
37      protected final JsonDeserializer<Object> _keyDeserializer;
38      /**
39       * If key instances have polymorphic type information, this
40       * is the type deserializer that can handle it
41       */
42      protected final TypeDeserializer _keyTypeDeserializer;
43  
44      /**
45       * Value deserializer.
46       */
47      protected final JsonDeserializer<Object> _valueDeserializer;
48  
49      /**
50       * If value instances have polymorphic type information, this
51       * is the type deserializer that can handle it
52       */
53      protected final TypeDeserializer _valueTypeDeserializer;
54  
55      /*
56      /**********************************************************
57      /* Life-cycle
58      /**********************************************************
59       */
60  
61      public PairDeserializer(
62          final JavaType type,
63          final JsonDeserializer<Object> keyDeser,
64          final TypeDeserializer keyTypeDeserializer,
65          final JsonDeserializer<Object> valueDeser,
66          final TypeDeserializer valueTypeDeser
67      ) {
68          super(type);
69          if (type.containedTypeCount() != 2) { // sanity check
70              throw new IllegalArgumentException("Missing generic type information for " + type);
71          }
72          this._type = type;
73          this._keyDeserializer = keyDeser;
74          this._keyTypeDeserializer = keyTypeDeserializer;
75          this._valueDeserializer = valueDeser;
76          this._valueTypeDeserializer = valueTypeDeser;
77      }
78  
79      /**
80       * Copy-constructor that can be used by sub-classes to allow
81       * copy-on-write styling copying of settings of an existing instance.
82       */
83      protected PairDeserializer(final PairDeserializer src) {
84          super(src._type);
85          this._type = src._type;
86          this._keyDeserializer = src._keyDeserializer;
87          this._keyTypeDeserializer = src._keyTypeDeserializer;
88          this._valueDeserializer = src._valueDeserializer;
89          this._valueTypeDeserializer = src._valueTypeDeserializer;
90      }
91  
92      protected PairDeserializer(
93          final PairDeserializer src,
94          final JsonDeserializer<Object> keyDeser,
95          final TypeDeserializer keyTypeDeserializer,
96          final JsonDeserializer<Object> valueDeser,
97          final TypeDeserializer valueTypeDeser
98      ) {
99          super(src._type);
100         this._type = src._type;
101         this._keyDeserializer = keyDeser;
102         this._keyTypeDeserializer = keyTypeDeserializer;
103         this._valueDeserializer = valueDeser;
104         this._valueTypeDeserializer = valueTypeDeser;
105     }
106 
107     /**
108      * Fluent factory method used to create a copy with slightly
109      * different settings. When sub-classing, MUST be overridden.
110      */
111     @SuppressWarnings("unchecked")
112     protected PairDeserializer withResolved(
113         final TypeDeserializer keyTypeDeserializer, final JsonDeserializer<?> keyDeser,
114         final TypeDeserializer valueTypeDeser, final JsonDeserializer<?> valueDeser
115     ) {
116         if ((this._keyDeserializer == keyDeser) && (this._valueDeserializer == valueDeser)
117             && (this._valueTypeDeserializer == valueTypeDeser)) {
118             return this;
119         }
120         return new PairDeserializer(
121             this,
122             (JsonDeserializer<Object>) keyDeser,
123             keyTypeDeserializer,
124             (JsonDeserializer<Object>) valueDeser,
125             valueTypeDeser);
126     }
127 
128     /*
129     /**********************************************************
130     /* Validation, post-processing (ResolvableDeserializer)
131     /**********************************************************
132      */
133 
134     /**
135      * Method called to finalize setup of this deserializer,
136      * when it is known for which property deserializer is needed for.
137      */
138     @Override
139     public JsonDeserializer<?> createContextual(
140         final DeserializationContext ctxt,
141         final BeanProperty property
142     ) throws JsonMappingException {
143         JsonDeserializer<?> kd = this._keyDeserializer;
144         kd = findConvertingContentDeserializer(ctxt, property, kd);
145         JavaType keyType = this._type.containedType(0);
146         if (kd == null) {
147             kd = ctxt.findContextualValueDeserializer(keyType, property);
148         } else { // if directly assigned, probably not yet contextual, so:
149             kd = ctxt.handleSecondaryContextualization(kd, property, keyType);
150         }
151         TypeDeserializer ktd = this._keyTypeDeserializer;
152         if (ktd != null) {
153             ktd = ktd.forProperty(property);
154         }
155 
156         JsonDeserializer<?> vd = this._valueDeserializer;
157         vd = findConvertingContentDeserializer(ctxt, property, vd);
158         JavaType valueType = this._type.containedType(1);
159         if (vd == null) {
160             vd = ctxt.findContextualValueDeserializer(valueType, property);
161         } else { // if directly assigned, probably not yet contextual, so:
162             vd = ctxt.handleSecondaryContextualization(vd, property, valueType);
163         }
164         TypeDeserializer vtd = this._valueTypeDeserializer;
165         if (vtd != null) {
166             vtd = vtd.forProperty(property);
167         }
168         return withResolved(ktd, kd, vtd, vd);
169     }
170 
171     /*
172     /**********************************************************
173     /* JsonDeserializer API
174     /**********************************************************
175      */
176 
177     @SuppressWarnings("unchecked")
178     @Override
179     public Pair<Object, Object> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
180         // Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
181         JsonToken t = p.getCurrentToken();
182         if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
183             // String may be ok however:
184             // slightly redundant (since String was passed above), but
185             return _deserializeFromEmpty(p, ctxt);
186         }
187         if (t == JsonToken.START_OBJECT) {
188             t = p.nextToken();
189         }
190         if (t != JsonToken.FIELD_NAME) {
191             if (t == JsonToken.END_OBJECT) {
192                 ctxt.reportMappingException("Can not deserialize a Pair out of empty JSON Object");
193                 return null;
194             }
195             return (Pair<Object, Object>) ctxt.handleUnexpectedToken(handledType(), p);
196         }
197 
198         Object key = null;
199         Object value = null;
200         Pair<String, Object> field = this.deserializeField(p, ctxt);
201         if ("left".equals(field.getKey())) {
202             key = field.getRight();
203         } else {
204             value = field.getRight();
205         }
206         t = p.nextToken();
207         field = this.deserializeField(p, ctxt);
208         if ("left".equals(field.getKey())) {
209             key = field.getRight();
210         } else {
211             value = field.getRight();
212         }
213 
214         // Close, but also verify that we reached the END_OBJECT
215         t = p.nextToken();
216         if (t != JsonToken.END_OBJECT) {
217             if (t == JsonToken.FIELD_NAME) { // most likely
218                 ctxt.reportMappingException("Problem binding JSON into Pair: more than one entry in JSON (second field: '" + p.getCurrentName() + "')");
219             } else {
220                 // how would this occur?
221                 ctxt.reportMappingException("Problem binding JSON into Pair: unexpected content after JSON Object entry: " + t);
222             }
223             return null;
224         }
225         return new ImmutablePair<>(key, value);
226     }
227 
228     private Pair<String, Object> deserializeField(
229         final JsonParser p,
230         final DeserializationContext ctxt
231     ) throws IOException {
232         final String fieldName = p.getCurrentName();
233         Object fieldValue = null;
234         if ("left".equals(fieldName)) {
235             final JsonDeserializer<Object> keyDes = this._keyDeserializer;
236             final TypeDeserializer keyTypeDeser = this._keyTypeDeserializer;
237             fieldValue = this.deserializeField(p, ctxt, fieldName, keyDes, keyTypeDeser);
238         } else if ("right".equals(fieldName)) {
239             final JsonDeserializer<Object> valueDes = this._valueDeserializer;
240             final TypeDeserializer valueTypeDeser = this._valueTypeDeserializer;
241             fieldValue = this.deserializeField(p, ctxt, fieldName, valueDes, valueTypeDeser);
242         } else {
243             ctxt.reportMappingException("Problem binding JSON into Pair: unknown field " + fieldName + ".");
244         }
245         return new ImmutablePair<>(fieldName, fieldValue);
246     }
247 
248     private Object deserializeField(
249         final JsonParser p,
250         final DeserializationContext ctxt,
251         final String fieldName,
252         final JsonDeserializer<Object> des,
253         final TypeDeserializer typeDeser
254     ) throws IOException {
255         Object fieldValue = null;
256         JsonToken t = p.nextToken();
257         try {
258             // Note: must handle null explicitly here; value deserializers won't
259             if (t == JsonToken.VALUE_NULL) {
260                 fieldValue = des.getNullValue(ctxt);
261             } else if (typeDeser == null) {
262                 fieldValue = des.deserialize(p, ctxt);
263             } else {
264                 fieldValue = des.deserializeWithType(p, ctxt, typeDeser);
265             }
266         } catch (final Exception ex) {
267             wrapAndThrow(ex, Pair.class, fieldName);
268         }
269         return fieldValue;
270     }
271 
272     @Override
273     public Pair<Object, Object> deserialize(
274         final JsonParser p, final DeserializationContext ctxt, final Pair<Object, Object> result
275     ) throws IOException {
276         throw new IllegalStateException("Can not update Pair values");
277     }
278 
279     @Override
280     public Object deserializeWithType(
281         final JsonParser p,
282         final DeserializationContext ctxt,
283         final TypeDeserializer typeDeserializer
284     ) throws IOException, JsonProcessingException {
285         // In future could check current token... for now this should be enough:
286         return typeDeserializer.deserializeTypedFromObject(p, ctxt);
287     }
288 
289     /*
290     /**********************************************************
291     /* Other public accessors
292     /**********************************************************
293      */
294 
295     @Override
296     public JavaType getValueType() {
297         return this._type;
298     }
299 
300     /*
301     /**********************************************************
302     /* Shared methods for sub-classes (from ContainerDeserializerBase)
303     /**********************************************************
304      */
305 
306     /**
307      * Helper method called by various Map(-like) deserializers.
308      */
309     protected void wrapAndThrow(Throwable t, final Object ref, String key) throws IOException {
310         // to handle StackOverflow:
311         while (t instanceof InvocationTargetException && t.getCause() != null) {
312             t = t.getCause();
313         }
314         // Errors and "plain" IOExceptions to be passed as is
315         if (t instanceof Error) {
316             throw (Error) t;
317         }
318         // ... except for mapping exceptions
319         if (t instanceof IOException && !(t instanceof JsonMappingException)) {
320             throw (IOException) t;
321         }
322         // for [databind#1141]
323         if (key == null) {
324             key = "N/A";
325         }
326         throw JsonMappingException.wrapWithPath(t, ref, key);
327     }
328 }