@@ -106,7 +106,6 @@ public EnumSet<Event> acceptedEvents() {
106
106
107
107
//---------------------------------------------------------------------------------------------
108
108
private static final EnumSet <Event > EventSetObject = EnumSet .of (Event .START_OBJECT , Event .KEY_NAME );
109
- private static final EnumSet <Event > EventSetObjectAndString = EnumSet .of (Event .START_OBJECT , Event .VALUE_STRING , Event .KEY_NAME );
110
109
111
110
private EnumSet <Event > acceptedEvents = EventSetObject ; // May be changed in `shortcutProperty()`
112
111
private final Supplier <ObjectType > constructor ;
@@ -115,6 +114,7 @@ public EnumSet<Event> acceptedEvents() {
115
114
private String typeProperty ;
116
115
private String defaultType ;
117
116
private FieldDeserializer <ObjectType > shortcutProperty ;
117
+ private boolean shortcutIsObject ;
118
118
private QuadConsumer <ObjectType , String , JsonParser , JsonpMapper > unknownFieldHandler ;
119
119
120
120
public ObjectDeserializer (Supplier <ObjectType > constructor ) {
@@ -133,6 +133,10 @@ public Set<String> fieldNames() {
133
133
return this .shortcutProperty == null ? null : this .shortcutProperty .name ;
134
134
}
135
135
136
+ public boolean shortcutIsObject () {
137
+ return this .shortcutIsObject ;
138
+ }
139
+
136
140
@ Override
137
141
public EnumSet <Event > nativeEvents () {
138
142
// May also return string if we have a shortcut property. This is needed to identify ambiguous unions.
@@ -145,33 +149,51 @@ public EnumSet<Event> acceptedEvents() {
145
149
}
146
150
147
151
public ObjectType deserialize (JsonParser parser , JsonpMapper mapper , Event event ) {
148
- return deserialize (constructor .get (), parser , mapper , event );
149
- }
150
-
151
- public ObjectType deserialize (ObjectType value , JsonParser parser , JsonpMapper mapper , Event event ) {
152
152
if (event == Event .VALUE_NULL ) {
153
153
return null ;
154
154
}
155
155
156
- String keyName = null ;
157
- String fieldName = null ;
156
+ ObjectType value = constructor .get ();
157
+ deserialize (value , parser , mapper , event );
158
+ return value ;
159
+ }
158
160
159
- try {
161
+ public void deserialize (ObjectType value , JsonParser parser , JsonpMapper mapper , Event event ) {
162
+ // Note: method is public as it's called by `withJson` to augment an already created object
160
163
161
- if (singleKey != null ) {
162
- // There's a wrapping property whose name is the key value
163
- if (event == Event .START_OBJECT ) {
164
- event = JsonpUtils .expectNextEvent (parser , Event .KEY_NAME );
165
- }
164
+ if (singleKey == null ) {
165
+ // Nominal case
166
+ deserializeInner (value , parser , mapper , event );
167
+
168
+ } else {
169
+ // Single key dictionary: there's a wrapping property whose name is the key value
170
+ if (event == Event .START_OBJECT ) {
171
+ event = JsonpUtils .expectNextEvent (parser , Event .KEY_NAME );
172
+ }
173
+
174
+ String keyName = parser .getString ();
175
+ try {
166
176
singleKey .deserialize (parser , mapper , null , value , event );
167
177
event = parser .next ();
178
+ deserializeInner (value , parser , mapper , event );
179
+ } catch (Exception e ) {
180
+ throw JsonpMappingException .from (e , value , keyName , parser );
168
181
}
169
182
170
- if (shortcutProperty != null && event != Event .START_OBJECT && event != Event .KEY_NAME ) {
171
- // This is the shortcut property (should be a value event, this will be checked by its deserializer)
172
- shortcutProperty .deserialize (parser , mapper , shortcutProperty .name , value , event );
183
+ JsonpUtils .expectNextEvent (parser , Event .END_OBJECT );
184
+ }
185
+ }
186
+
187
+ private void deserializeInner (ObjectType value , JsonParser parser , JsonpMapper mapper , Event event ) {
188
+ String fieldName = null ;
173
189
174
- } else if (typeProperty == null ) {
190
+ try {
191
+ if ((parser = deserializeWithShortcut (value , parser , mapper , event )) == null ) {
192
+ // We found the shortcut form
193
+ return ;
194
+ }
195
+
196
+ if (typeProperty == null ) {
175
197
if (event != Event .START_OBJECT && event != Event .KEY_NAME ) {
176
198
// Report we're waiting for a start_object, since this is the most common beginning for object parser
177
199
JsonpUtils .expectEvent (parser , Event .START_OBJECT , event );
@@ -209,16 +231,52 @@ public ObjectType deserialize(ObjectType value, JsonParser parser, JsonpMapper m
209
231
fieldDeserializer .deserialize (innerParser , mapper , variant , value );
210
232
}
211
233
}
234
+ } catch (Exception e ) {
235
+ // Add field name if present
236
+ throw JsonpMappingException .from (e , value , fieldName , parser );
237
+ }
238
+ }
212
239
213
- if (singleKey != null ) {
214
- JsonpUtils .expectNextEvent (parser , Event .END_OBJECT );
240
+ /**
241
+ * Try to deserialize the value with its shortcut property, if any.
242
+ *
243
+ * @return {@code null} if the shortcut form was found, and otherwise a parser that should be used to parse the
244
+ * non-shortcut form. It may be different from the orginal parser if look-ahead was needed.
245
+ */
246
+ private JsonParser deserializeWithShortcut (ObjectType value , JsonParser parser , JsonpMapper mapper , Event event ) {
247
+ if (shortcutProperty != null ) {
248
+ if (!shortcutIsObject ) {
249
+ if (event != Event .START_OBJECT && event != Event .KEY_NAME ) {
250
+ // This is the shortcut property (should be a value or array event, this will be checked by its deserializer)
251
+ shortcutProperty .deserialize (parser , mapper , shortcutProperty .name , value , event );
252
+ return null ;
253
+ }
254
+ } else {
255
+ // Fast path: we don't need to look ahead if the current event isn't an object
256
+ if (event != Event .START_OBJECT ) {
257
+ shortcutProperty .deserialize (parser , mapper , shortcutProperty .name , value , event );
258
+ return null ;
259
+ }
260
+
261
+ // Look ahead: does the shortcut property exist? If yes, the shortcut is used
262
+ Map .Entry <Object , JsonParser > shortcut = JsonpUtils .findVariant (
263
+ Collections .singletonMap (shortcutProperty .name , Boolean .TRUE /* arbitrary non-null value */ ),
264
+ parser , mapper
265
+ );
266
+
267
+ // Parse the buffered events
268
+ parser = shortcut .getValue ();
269
+ event = parser .next ();
270
+
271
+ // If shortcut property was not found, this is a shortcut. Otherwise, keep deserializing as usual
272
+ if (shortcut .getKey () == null ) {
273
+ shortcutProperty .deserialize (parser , mapper , shortcutProperty .name , value , event );
274
+ return null ;
275
+ }
215
276
}
216
- } catch (Exception e ) {
217
- // Add key name (for single key dicts) and field name if present
218
- throw JsonpMappingException .from (e , value , fieldName , parser ).prepend (value , keyName );
219
277
}
220
278
221
- return value ;
279
+ return parser ;
222
280
}
223
281
224
282
protected void parseUnknownField (JsonParser parser , JsonpMapper mapper , String fieldName , ObjectType object ) {
@@ -249,14 +307,18 @@ public void ignore(String name) {
249
307
}
250
308
251
309
public void shortcutProperty (String name ) {
310
+ shortcutProperty (name , false );
311
+ }
312
+
313
+ public void shortcutProperty (String name , boolean isObject ) {
252
314
this .shortcutProperty = this .fieldDeserializers .get (name );
253
315
if (this .shortcutProperty == null ) {
254
316
throw new NoSuchElementException ("No deserializer was setup for '" + name + "'" );
255
317
}
256
318
257
- // acceptedEvents = EnumSet.copyOf(acceptedEvents);
258
- // acceptedEvents.addAll(shortcutProperty.acceptedEvents());
259
- acceptedEvents = EventSetObjectAndString ;
319
+ acceptedEvents = EnumSet .copyOf (acceptedEvents );
320
+ acceptedEvents .addAll (shortcutProperty .acceptedEvents ());
321
+ this . shortcutIsObject = isObject ;
260
322
}
261
323
262
324
//----- Object types
0 commit comments