Skip to content

Commit a147385

Browse files
committed
Fix element ordering issue on a mapped de/serialized entity having List property.
Closes #2565
1 parent dd75966 commit a147385

File tree

3 files changed

+181
-85
lines changed

3 files changed

+181
-85
lines changed

src/main/java/org/springframework/data/redis/hash/Jackson2HashMapper.java

+105-70
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
*/
1616
package org.springframework.data.redis.hash;
1717

18-
import static com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.*;
18+
import static com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping.EVERYTHING;
1919

2020
import java.io.IOException;
2121
import java.text.ParseException;
2222
import java.util.ArrayList;
23+
import java.util.Arrays;
2324
import java.util.Calendar;
25+
import java.util.Collections;
2426
import java.util.Date;
2527
import java.util.HashMap;
2628
import java.util.Iterator;
@@ -32,11 +34,13 @@
3234
import java.util.Set;
3335

3436
import org.springframework.data.mapping.MappingException;
37+
import org.springframework.data.redis.support.collections.CollectionUtils;
3538
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
39+
import org.springframework.lang.NonNull;
40+
import org.springframework.lang.Nullable;
3641
import org.springframework.util.Assert;
3742
import org.springframework.util.ClassUtils;
3843
import org.springframework.util.NumberUtils;
39-
import org.springframework.util.ObjectUtils;
4044
import org.springframework.util.StringUtils;
4145

4246
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -233,8 +237,12 @@ public Object fromHash(Map<String, Object> hash) {
233237

234238
if (flatten) {
235239

236-
return typingMapper.reader().forType(Object.class)
237-
.readValue(untypedMapper.writeValueAsBytes(doUnflatten(hash)));
240+
Map<String, Object> unflattenedHash = doUnflatten(hash);
241+
byte[] unflattenedHashedBytes = untypedMapper.writeValueAsBytes(unflattenedHash);
242+
Object hashedObject = typingMapper.reader().forType(Object.class)
243+
.readValue(unflattenedHashedBytes);;
244+
245+
return hashedObject;
238246
}
239247

240248
return typingMapper.treeToValue(untypedMapper.valueToTree(hash), Object.class);
@@ -248,31 +256,37 @@ public Object fromHash(Map<String, Object> hash) {
248256
private Map<String, Object> doUnflatten(Map<String, Object> source) {
249257

250258
Map<String, Object> result = new LinkedHashMap<>();
251-
Set<String> treatSeperate = new LinkedHashSet<>();
259+
Set<String> treatSeparate = new LinkedHashSet<>();
260+
252261
for (Entry<String, Object> entry : source.entrySet()) {
253262

254263
String key = entry.getKey();
255-
String[] args = key.split("\\.");
264+
String[] keyParts = key.split("\\.");
256265

257-
if (args.length == 1 && !args[0].contains("[")) {
266+
if (keyParts.length == 1 && isNotIndexed(keyParts[0])) {
258267
result.put(entry.getKey(), entry.getValue());
259268
continue;
260269
}
261270

262-
if (args.length == 1 && args[0].contains("[")) {
271+
if (keyParts.length == 1 && isIndexed(keyParts[0])) {
263272

264-
String prunedKey = args[0].substring(0, args[0].indexOf('['));
265-
if (result.containsKey(prunedKey)) {
266-
appendValueToTypedList(args[0], entry.getValue(), (List<Object>) result.get(prunedKey));
267-
} else {
268-
result.put(prunedKey, createTypedListWithValue(entry.getValue()));
273+
String indexedKeyName = keyParts[0];
274+
String nonIndexedKeyName = stripIndex(indexedKeyName);
275+
276+
int index = getIndex(indexedKeyName);
277+
278+
if (result.containsKey(nonIndexedKeyName)) {
279+
addValueToTypedListAtIndex((List<Object>) result.get(nonIndexedKeyName), index, entry.getValue());
280+
}
281+
else {
282+
result.put(nonIndexedKeyName, createTypedListWithValue(index, entry.getValue()));
269283
}
270284
} else {
271-
treatSeperate.add(key.substring(0, key.indexOf('.')));
285+
treatSeparate.add(key.substring(0, key.indexOf('.')));
272286
}
273287
}
274288

275-
for (String partial : treatSeperate) {
289+
for (String partial : treatSeparate) {
276290

277291
Map<String, Object> newSource = new LinkedHashMap<>();
278292

@@ -284,12 +298,13 @@ private Map<String, Object> doUnflatten(Map<String, Object> source) {
284298

285299
if (partial.endsWith("]")) {
286300

287-
String prunedKey = partial.substring(0, partial.indexOf('['));
301+
String nonIndexPartial = stripIndex(partial);
302+
int index = getIndex(partial);
288303

289-
if (result.containsKey(prunedKey)) {
290-
appendValueToTypedList(partial, doUnflatten(newSource), (List<Object>) result.get(prunedKey));
304+
if (result.containsKey(nonIndexPartial)) {
305+
addValueToTypedListAtIndex((List<Object>) result.get(nonIndexPartial), index, doUnflatten(newSource));
291306
} else {
292-
result.put(prunedKey, createTypedListWithValue(doUnflatten(newSource)));
307+
result.put(nonIndexPartial, createTypedListWithValue(index, doUnflatten(newSource)));
293308
}
294309
} else {
295310
result.put(partial, doUnflatten(newSource));
@@ -299,6 +314,27 @@ private Map<String, Object> doUnflatten(Map<String, Object> source) {
299314
return result;
300315
}
301316

317+
private boolean isIndexed(@NonNull String value) {
318+
return value.indexOf('[') > -1;
319+
}
320+
321+
private boolean isNotIndexed(@NonNull String value) {
322+
return !isIndexed(value);
323+
}
324+
325+
private int getIndex(@NonNull String indexedValue) {
326+
return Integer.parseInt(indexedValue.substring(indexedValue.indexOf('[') + 1, indexedValue.length() - 1));
327+
}
328+
329+
private @NonNull String stripIndex(@NonNull String indexedValue) {
330+
331+
int indexOfLeftBracket = indexedValue.indexOf("[");
332+
333+
return indexOfLeftBracket > -1
334+
? indexedValue.substring(0, indexOfLeftBracket)
335+
: indexedValue;
336+
}
337+
302338
private Map<String, Object> flattenMap(Iterator<Entry<String, JsonNode>> source) {
303339

304340
Map<String, Object> resultMap = new HashMap<>();
@@ -314,7 +350,6 @@ private void doFlatten(String propertyPrefix, Iterator<Entry<String, JsonNode>>
314350
}
315351

316352
while (inputMap.hasNext()) {
317-
318353
Entry<String, JsonNode> entry = inputMap.next();
319354
flattenElement(propertyPrefix + entry.getKey(), entry.getValue(), resultMap);
320355
}
@@ -323,7 +358,6 @@ private void doFlatten(String propertyPrefix, Iterator<Entry<String, JsonNode>>
323358
private void flattenElement(String propertyPrefix, Object source, Map<String, Object> resultMap) {
324359

325360
if (!(source instanceof JsonNode)) {
326-
327361
resultMap.put(propertyPrefix, source);
328362
return;
329363
}
@@ -337,6 +371,7 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
337371
while (nodes.hasNext()) {
338372

339373
JsonNode cur = nodes.next();
374+
340375
if (cur.isArray()) {
341376
this.flattenCollection(propertyPrefix, cur.elements(), resultMap);
342377
} else {
@@ -370,12 +405,13 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
370405

371406
try {
372407
resultMap.put(propertyPrefix, next.binaryValue());
373-
} catch (IOException e) {
374-
throw new IllegalStateException(String.format("Cannot read binary value of '%s'", propertyPrefix), e);
408+
} catch (IOException cause) {
409+
String message = String.format("Cannot read binary value of '%s'", propertyPrefix);
410+
throw new IllegalStateException(message, cause);
375411
}
412+
376413
break;
377414
}
378-
379415
}
380416
}
381417
}
@@ -390,53 +426,49 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
390426
private boolean mightBeJavaType(JsonNode node) {
391427

392428
String textValue = node.asText();
393-
if (!SOURCE_VERSION_PRESENT) {
394-
395-
if (ObjectUtils.nullSafeEquals(textValue, "java.util.Date")) {
396-
return true;
397-
}
398-
if (ObjectUtils.nullSafeEquals(textValue, "java.math.BigInteger")) {
399-
return true;
400-
}
401-
if (ObjectUtils.nullSafeEquals(textValue, "java.math.BigDecimal")) {
402-
return true;
403-
}
404429

405-
return false;
430+
if (!SOURCE_VERSION_PRESENT) {
431+
return Arrays.asList("java.util.Date", "java.math.BigInteger", "java.math.BigDecimal").contains(textValue);
406432
}
407-
return javax.lang.model.SourceVersion.isName(textValue);
408433

434+
return javax.lang.model.SourceVersion.isName(textValue);
409435
}
410436

411437
private void flattenCollection(String propertyPrefix, Iterator<JsonNode> list, Map<String, Object> resultMap) {
412438

413-
int counter = 0;
414-
while (list.hasNext()) {
439+
for (int counter = 0; list.hasNext(); counter++) {
415440
JsonNode element = list.next();
416441
flattenElement(propertyPrefix + "[" + counter + "]", element, resultMap);
417-
counter++;
418442
}
419443
}
420444

421445
@SuppressWarnings("unchecked")
422-
private void appendValueToTypedList(String key, Object value, List<Object> destination) {
446+
private void addValueToTypedListAtIndex(List<Object> listWithTypeHint, int index, Object value) {
423447

424-
int index = Integer.parseInt(key.substring(key.indexOf('[') + 1, key.length() - 1));
425-
List<Object> resultList = ((List<Object>) destination.get(1));
426-
if (resultList.size() < index) {
427-
resultList.add(value);
428-
} else {
429-
resultList.add(index, value);
448+
List<Object> valueList = (List<Object>) listWithTypeHint.get(1);
449+
450+
if (index >= valueList.size()) {
451+
int initialCapacity = index + 1;
452+
List<Object> newValueList = new ArrayList<>(initialCapacity);
453+
Collections.copy(CollectionUtils.initializeList(newValueList, initialCapacity), valueList);
454+
listWithTypeHint.set(1, newValueList);
455+
valueList = newValueList;
430456
}
457+
458+
valueList.set(index, value);
431459
}
432460

433-
private List<Object> createTypedListWithValue(Object value) {
461+
private List<Object> createTypedListWithValue(int index, Object value) {
462+
463+
int initialCapacity = index + 1;
464+
465+
List<Object> valueList = CollectionUtils.initializeList(new ArrayList<>(initialCapacity), initialCapacity);
466+
valueList.set(index, value);
434467

435468
List<Object> listWithTypeHint = new ArrayList<>();
436-
listWithTypeHint.add(ArrayList.class.getName()); // why jackson? why?
437-
List<Object> values = new ArrayList<>();
438-
values.add(value);
439-
listWithTypeHint.add(values);
469+
listWithTypeHint.add(ArrayList.class.getName());
470+
listWithTypeHint.add(valueList);
471+
440472
return listWithTypeHint;
441473
}
442474

@@ -468,16 +500,16 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, Typ
468500
@Override
469501
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
470502

471-
Object val = delegate.deserialize(p, ctxt);
503+
Object value = delegate.deserialize(p, ctxt);
472504

473-
if (val instanceof Date) {
474-
return (Date) val;
505+
if (value instanceof Date) {
506+
return (Date) value;
475507
}
476508

477509
try {
478-
return ctxt.getConfig().getDateFormat().parse(val.toString());
479-
} catch (ParseException e) {
480-
return new Date(NumberUtils.parseNumber(val.toString(), Long.class));
510+
return ctxt.getConfig().getDateFormat().parse(value.toString());
511+
} catch (ParseException cause) {
512+
return new Date(NumberUtils.parseNumber(value.toString(), Long.class));
481513
}
482514
}
483515
}
@@ -500,13 +532,13 @@ public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IO
500532

501533
Date date = dateDeserializer.deserialize(p, ctxt);
502534

503-
if (date == null) {
504-
return null;
535+
if (date != null) {
536+
Calendar calendar = Calendar.getInstance();
537+
calendar.setTime(date);
538+
return calendar;
505539
}
506540

507-
Calendar calendar = Calendar.getInstance();
508-
calendar.setTime(date);
509-
return calendar;
541+
return null;
510542
}
511543
}
512544

@@ -524,17 +556,20 @@ private static class UntypedSerializer<T> extends JsonSerializer<T> {
524556
}
525557

526558
@Override
527-
public void serializeWithType(T value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer)
528-
throws IOException {
529-
serialize(value, gen, serializers);
559+
public void serializeWithType(T value, JsonGenerator jsonGenerator, SerializerProvider serializers,
560+
TypeSerializer typeSerializer) throws IOException {
561+
562+
serialize(value, jsonGenerator, serializers);
530563
}
531564

532565
@Override
533-
public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
534-
if (value == null) {
535-
serializers.defaultSerializeNull(gen);
566+
public void serialize(@Nullable T value, JsonGenerator jsonGenerator, SerializerProvider serializers)
567+
throws IOException {
568+
569+
if (value != null) {
570+
delegate.serialize(value, jsonGenerator, serializers);
536571
} else {
537-
delegate.serialize(value, gen, serializers);
572+
serializers.defaultSerializeNull(jsonGenerator);
538573
}
539574
}
540575
}

0 commit comments

Comments
 (0)