85
85
* Date date;
86
86
* LocalDateTime localDateTime;
87
87
* }
88
- *
88
+ * <p>
89
89
* class Address {
90
90
* String city;
91
91
* String country;
153
153
*
154
154
* @author Christoph Strobl
155
155
* @author Mark Paluch
156
+ * @author John Blum
156
157
* @since 1.8
157
158
*/
158
159
public class Jackson2HashMapper implements HashMapper <Object , String , Object > {
159
160
160
- private static final boolean SOURCE_VERSION_PRESENT = ClassUtils .isPresent ("javax.lang.model.SourceVersion" , Jackson2HashMapper .class .getClassLoader ());
161
-
162
- private final HashMapperModule HASH_MAPPER_MODULE = new HashMapperModule ();
161
+ private static final boolean SOURCE_VERSION_PRESENT =
162
+ ClassUtils .isPresent ("javax.lang.model.SourceVersion" , Jackson2HashMapper .class .getClassLoader ());
163
163
164
164
private final ObjectMapper typingMapper ;
165
165
private final ObjectMapper untypedMapper ;
166
166
private final boolean flatten ;
167
167
168
168
/**
169
- * Creates new {@link Jackson2HashMapper} with default {@link ObjectMapper}.
169
+ * Creates new {@link Jackson2HashMapper} with a default {@link ObjectMapper}.
170
170
*
171
- * @param flatten
171
+ * @param flatten boolean used to configure whether JSON de/serialized {@link Object} properties
172
+ * will be un/flattened using {@literal dot notation}, or whether to retain the hierarchical node structure
173
+ * created by Jackson.
172
174
*/
173
175
public Jackson2HashMapper (boolean flatten ) {
174
176
175
177
this (new ObjectMapper () {
176
178
177
179
@ Override
178
180
protected TypeResolverBuilder <?> _constructDefaultTypeResolverBuilder (DefaultTyping applicability ,
179
- PolymorphicTypeValidator ptv ) {
180
- return new DefaultTypeResolverBuilder ( applicability , ptv ) {
181
- public boolean useForType ( JavaType t ) {
181
+ PolymorphicTypeValidator typeValidator ) {
182
+
183
+ return new DefaultTypeResolverBuilder ( applicability , typeValidator ) {
182
184
183
- if (t .isPrimitive ()) {
185
+ public boolean useForType (JavaType type ) {
186
+
187
+ if (type .isPrimitive ()) {
184
188
return false ;
185
189
}
186
190
187
- if (flatten && t .isTypeOrSubTypeOf (Number .class )) {
191
+ if (flatten && type .isTypeOrSubTypeOf (Number .class )) {
188
192
return false ;
189
193
}
190
194
191
195
if (EVERYTHING .equals (_appliesFor )) {
192
- return !TreeNode .class .isAssignableFrom (t .getRawClass ());
196
+ return !TreeNode .class .isAssignableFrom (type .getRawClass ());
193
197
}
194
198
195
- return super .useForType (t );
199
+ return super .useForType (type );
196
200
}
197
201
};
198
202
}
199
203
}.findAndRegisterModules (), flatten );
200
204
201
- typingMapper .activateDefaultTyping (typingMapper .getPolymorphicTypeValidator (), DefaultTyping .EVERYTHING ,
202
- As .PROPERTY );
203
- typingMapper .configure (SerializationFeature .WRITE_NULL_MAP_VALUES , false );
205
+ this .typingMapper .activateDefaultTyping (this .typingMapper .getPolymorphicTypeValidator (),
206
+ DefaultTyping .EVERYTHING , As .PROPERTY );
207
+ this .typingMapper .configure (SerializationFeature .WRITE_NULL_MAP_VALUES , false );
208
+
204
209
if (flatten ) {
205
- typingMapper .disable (MapperFeature .REQUIRE_TYPE_ID_FOR_SUBTYPES );
210
+ this . typingMapper .disable (MapperFeature .REQUIRE_TYPE_ID_FOR_SUBTYPES );
206
211
}
207
212
208
213
// Prevent splitting time types into arrays. E
209
- typingMapper .configure (SerializationFeature .WRITE_DATES_AS_TIMESTAMPS , false );
210
- typingMapper .setSerializationInclusion ( Include . NON_NULL );
211
- typingMapper .configure ( DeserializationFeature . FAIL_ON_UNKNOWN_PROPERTIES , false );
212
- typingMapper .registerModule (HASH_MAPPER_MODULE );
214
+ this . typingMapper .configure (SerializationFeature .WRITE_DATES_AS_TIMESTAMPS , false );
215
+ this . typingMapper .configure ( DeserializationFeature . FAIL_ON_UNKNOWN_PROPERTIES , false );
216
+ this . typingMapper .setSerializationInclusion ( Include . NON_NULL );
217
+ this . typingMapper .registerModule (new HashMapperModule () );
213
218
}
214
219
215
220
/**
216
- * Creates new {@link Jackson2HashMapper}.
221
+ * Creates new {@link Jackson2HashMapper} initialized with a custom Jackson {@link ObjectMapper} .
217
222
*
218
- * @param mapper must not be {@literal null}.
219
- * @param flatten
223
+ * @param mapper Jackson {@link ObjectMapper} used to de/serialize hashed {@link Object objects};
224
+ * must not be {@literal null}.
225
+ * @param flatten boolean used to configure whether JSON de/serialized {@link Object} properties
226
+ * will be un/flattened using {@literal dot notation}, or whether to retain the hierarchical node structure
227
+ * created by Jackson.
220
228
*/
221
229
public Jackson2HashMapper (ObjectMapper mapper , boolean flatten ) {
222
230
223
231
Assert .notNull (mapper , "Mapper must not be null" );
224
- this .typingMapper = mapper ;
225
- this .flatten = flatten ;
226
232
233
+ this .flatten = flatten ;
234
+ this .typingMapper = mapper ;
227
235
this .untypedMapper = new ObjectMapper ();
228
- untypedMapper .findAndRegisterModules ();
229
236
this .untypedMapper .configure (SerializationFeature .WRITE_NULL_MAP_VALUES , false );
230
237
this .untypedMapper .setSerializationInclusion (Include .NON_NULL );
238
+ this .untypedMapper .findAndRegisterModules ();
231
239
}
232
240
233
241
@ Override
234
242
@ SuppressWarnings ("unchecked" )
235
243
public Map <String , Object > toHash (Object source ) {
236
244
237
- JsonNode tree = typingMapper .valueToTree (source );
238
- return flatten ? flattenMap (tree .fields ()) : untypedMapper .convertValue (tree , Map .class );
245
+ JsonNode tree = this .typingMapper .valueToTree (source );
246
+
247
+ return this .flatten ? flattenMap (tree .fields ()) : this .untypedMapper .convertValue (tree , Map .class );
239
248
}
240
249
241
250
@ Override
251
+ @ SuppressWarnings ("all" )
242
252
public Object fromHash (Map <String , Object > hash ) {
243
253
244
254
try {
245
-
246
- if (flatten ) {
255
+ if (this .flatten ) {
247
256
248
257
Map <String , Object > unflattenedHash = doUnflatten (hash );
249
- byte [] unflattenedHashedBytes = untypedMapper .writeValueAsBytes (unflattenedHash );
250
- Object hashedObject = typingMapper .reader ().forType (Object .class )
258
+ byte [] unflattenedHashedBytes = this . untypedMapper .writeValueAsBytes (unflattenedHash );
259
+ Object hashedObject = this . typingMapper .reader ().forType (Object .class )
251
260
.readValue (unflattenedHashedBytes );
252
261
253
262
return hashedObject ;
254
263
}
255
264
256
- return typingMapper .treeToValue (untypedMapper .valueToTree (hash ), Object .class );
265
+ return this . typingMapper .treeToValue (this . untypedMapper .valueToTree (hash ), Object .class );
257
266
258
- } catch (IOException e ) {
259
- throw new MappingException (e .getMessage (), e );
267
+ } catch (IOException cause ) {
268
+ throw new MappingException (cause .getMessage (), cause );
260
269
}
261
270
}
262
271
@@ -272,11 +281,8 @@ private Map<String, Object> doUnflatten(Map<String, Object> source) {
272
281
String [] keyParts = key .split ("\\ ." );
273
282
274
283
if (keyParts .length == 1 && isNotIndexed (keyParts [0 ])) {
275
- result .put (entry .getKey (), entry .getValue ());
276
- continue ;
277
- }
278
-
279
- if (keyParts .length == 1 && isIndexed (keyParts [0 ])) {
284
+ result .put (key , entry .getValue ());
285
+ } else if (keyParts .length == 1 && isIndexed (keyParts [0 ])) {
280
286
281
287
String indexedKeyName = keyParts [0 ];
282
288
String nonIndexedKeyName = stripIndex (indexedKeyName );
@@ -290,21 +296,25 @@ private Map<String, Object> doUnflatten(Map<String, Object> source) {
290
296
result .put (nonIndexedKeyName , createTypedListWithValue (index , entry .getValue ()));
291
297
}
292
298
} else {
293
- treatSeparate .add (key . substring ( 0 , key . indexOf ( '.' )) );
299
+ treatSeparate .add (keyParts [ 0 ] );
294
300
}
295
301
}
296
302
297
303
for (String partial : treatSeparate ) {
298
304
299
305
Map <String , Object > newSource = new LinkedHashMap <>();
300
306
307
+ // Copies all nested, dot properties from the source Map to the new Map beginning from
308
+ // the next nested (dot) property
301
309
for (Entry <String , Object > entry : source .entrySet ()) {
302
- if (entry .getKey ().startsWith (partial )) {
303
- newSource .put (entry .getKey ().substring (partial .length () + 1 ), entry .getValue ());
310
+ String key = entry .getKey ();
311
+ if (key .startsWith (partial )) {
312
+ String keyAfterDot = key .substring (partial .length () + 1 );
313
+ newSource .put (keyAfterDot , entry .getValue ());
304
314
}
305
315
}
306
316
307
- if (partial . endsWith ( "]" )) {
317
+ if (isNonNestedIndexed ( partial )) {
308
318
309
319
String nonIndexPartial = stripIndex (partial );
310
320
int index = getIndex (partial );
@@ -330,6 +340,10 @@ private boolean isNotIndexed(@NonNull String value) {
330
340
return !isIndexed (value );
331
341
}
332
342
343
+ private boolean isNonNestedIndexed (@ NonNull String value ) {
344
+ return value .endsWith ("]" );
345
+ }
346
+
333
347
private int getIndex (@ NonNull String indexedValue ) {
334
348
return Integer .parseInt (indexedValue .substring (indexedValue .indexOf ('[' ) + 1 , indexedValue .length () - 1 ));
335
349
}
@@ -346,7 +360,7 @@ private int getIndex(@NonNull String indexedValue) {
346
360
private Map <String , Object > flattenMap (Iterator <Entry <String , JsonNode >> source ) {
347
361
348
362
Map <String , Object > resultMap = new HashMap <>();
349
- this . doFlatten ("" , source , resultMap );
363
+ doFlatten ("" , source , resultMap );
350
364
return resultMap ;
351
365
}
352
366
@@ -378,56 +392,52 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
378
392
379
393
while (nodes .hasNext ()) {
380
394
381
- JsonNode cur = nodes .next ();
382
-
383
- if (cur .isArray ()) {
384
- this .flattenCollection (propertyPrefix , cur .elements (), resultMap );
385
- } else {
386
- if (nodes .hasNext () && mightBeJavaType (cur )) {
395
+ JsonNode currentNode = nodes .next ();
387
396
388
- JsonNode next = nodes .next ();
397
+ if (currentNode .isArray ()) {
398
+ flattenCollection (propertyPrefix , currentNode .elements (), resultMap );
399
+ } else if (nodes .hasNext () && mightBeJavaType (currentNode )) {
389
400
390
- if (next .isArray ()) {
391
- this .flattenCollection (propertyPrefix , next .elements (), resultMap );
392
- }
401
+ JsonNode next = nodes .next ();
393
402
394
- if (cur .asText ().equals ("java.util.Date" )) {
395
- resultMap .put (propertyPrefix , next .asText ());
396
- break ;
397
- }
398
- if (next .isNumber ()) {
399
- resultMap .put (propertyPrefix , next .numberValue ());
400
- break ;
401
- }
402
- if (next .isTextual ()) {
403
+ if (next .isArray ()) {
404
+ flattenCollection (propertyPrefix , next .elements (), resultMap );
405
+ }
406
+ if (currentNode .asText ().equals ("java.util.Date" )) {
407
+ resultMap .put (propertyPrefix , next .asText ());
408
+ break ;
409
+ }
410
+ if (next .isNumber ()) {
411
+ resultMap .put (propertyPrefix , next .numberValue ());
412
+ break ;
413
+ }
414
+ if (next .isTextual ()) {
415
+ resultMap .put (propertyPrefix , next .textValue ());
416
+ break ;
417
+ }
418
+ if (next .isBoolean ()) {
419
+ resultMap .put (propertyPrefix , next .booleanValue ());
420
+ break ;
421
+ }
422
+ if (next .isBinary ()) {
403
423
404
- resultMap . put ( propertyPrefix , next . textValue ());
405
- break ;
424
+ try {
425
+ resultMap . put ( propertyPrefix , next . binaryValue ()) ;
406
426
}
407
- if (next .isBoolean ()) {
408
-
409
- resultMap .put (propertyPrefix , next .booleanValue ());
410
- break ;
427
+ catch (IOException cause ) {
428
+ String message = String .format ("Cannot read binary value of '%s'" , propertyPrefix );
429
+ throw new IllegalStateException (message , cause );
411
430
}
412
- if (next .isBinary ()) {
413
431
414
- try {
415
- resultMap .put (propertyPrefix , next .binaryValue ());
416
- } catch (IOException cause ) {
417
- String message = String .format ("Cannot read binary value of '%s'" , propertyPrefix );
418
- throw new IllegalStateException (message , cause );
419
- }
420
-
421
- break ;
422
- }
432
+ break ;
423
433
}
424
434
}
425
435
}
426
-
427
436
} else if (element .isContainerNode ()) {
428
- this . doFlatten (propertyPrefix , element .fields (), resultMap );
437
+ doFlatten (propertyPrefix , element .fields (), resultMap );
429
438
} else {
430
- resultMap .put (propertyPrefix , new DirectFieldAccessFallbackBeanWrapper (element ).getPropertyValue ("_value" ));
439
+ resultMap .put (propertyPrefix , new DirectFieldAccessFallbackBeanWrapper (element )
440
+ .getPropertyValue ("_value" ));
431
441
}
432
442
}
433
443
0 commit comments