Skip to content

Commit 2708892

Browse files
committed
Fix #2733
1 parent 729d013 commit 2708892

File tree

2 files changed

+117
-12
lines changed

2 files changed

+117
-12
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Project: jackson-databind
2727
an explicit name
2828
(reported, fix contributed by David B)
2929
#2732: Allow `JsonNode` auto-convert into `ArrayNode` if duplicates found (for XML)
30+
#2733: Allow values of "untyped" auto-convert into `List` if duplicates found (for XML)
3031
- Add `BeanDeserializerBase.isCaseInsensitive()`
3132
- Some refactoring of `CollectionDeserializer` to solve CSV array handling issues
3233

src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java

+116-12
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,11 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
197197
&& getClass() == UntypedObjectDeserializer.class) {
198198
return Vanilla.instance(preventMerge);
199199
}
200+
200201
if (preventMerge != _nonMerging) {
201202
return new UntypedObjectDeserializer(this, preventMerge);
202203
}
204+
203205
return this;
204206
}
205207

@@ -501,44 +503,92 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
501503
}
502504
if (key1 == null) {
503505
// empty map might work; but caller may want to modify... so better just give small modifiable
504-
return new LinkedHashMap<String,Object>(2);
506+
return new LinkedHashMap<>(2);
505507
}
506508
// minor optimization; let's handle 1 and 2 entry cases separately
507509
// 24-Mar-2015, tatu: Ideally, could use one of 'nextXxx()' methods, but for
508510
// that we'd need new method(s) in JsonDeserializer. So not quite yet.
509511
p.nextToken();
510512
Object value1 = deserialize(p, ctxt);
511-
512513
String key2 = p.nextFieldName();
513514
if (key2 == null) { // has to be END_OBJECT, then
514515
// single entry; but we want modifiable
515-
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(2);
516+
LinkedHashMap<String, Object> result = new LinkedHashMap<>(2);
516517
result.put(key1, value1);
517518
return result;
518519
}
519520
p.nextToken();
520521
Object value2 = deserialize(p, ctxt);
521522

522523
String key = p.nextFieldName();
523-
524524
if (key == null) {
525-
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4);
525+
LinkedHashMap<String, Object> result = new LinkedHashMap<>(4);
526526
result.put(key1, value1);
527-
result.put(key2, value2);
527+
if (result.put(key2, value2) != null) {
528+
// 22-May-2020, tatu: [databind#2733] may need extra handling
529+
return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
530+
}
528531
return result;
529532
}
530533
// And then the general case; default map size is 16
531-
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
534+
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
532535
result.put(key1, value1);
533-
result.put(key2, value2);
536+
if (result.put(key2, value2) != null) {
537+
// 22-May-2020, tatu: [databind#2733] may need extra handling
538+
return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
539+
}
534540

535541
do {
536542
p.nextToken();
537-
result.put(key, deserialize(p, ctxt));
543+
final Object newValue = deserialize(p, ctxt);
544+
final Object oldValue = result.put(key, newValue);
545+
if (oldValue != null) {
546+
return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
547+
p.nextFieldName());
548+
}
538549
} while ((key = p.nextFieldName()) != null);
539550
return result;
540551
}
541552

553+
// @since 2.12 (wrt [databind#2733]
554+
protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
555+
final Map<String, Object> result, String key,
556+
Object oldValue, Object newValue, String nextKey) throws IOException
557+
{
558+
final boolean squashDups = ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES);
559+
560+
if (squashDups) {
561+
_squashDups(result, key, oldValue, newValue);
562+
}
563+
564+
while (nextKey != null) {
565+
p.nextToken();
566+
newValue = deserialize(p, ctxt);
567+
oldValue = result.put(nextKey, newValue);
568+
if ((oldValue != null) && squashDups) {
569+
_squashDups(result, key, oldValue, newValue);
570+
}
571+
nextKey = p.nextFieldName();
572+
}
573+
574+
return result;
575+
}
576+
577+
@SuppressWarnings("unchecked")
578+
private void _squashDups(final Map<String, Object> result, String key,
579+
Object oldValue, Object newValue)
580+
{
581+
if (oldValue instanceof List<?>) {
582+
((List<Object>) oldValue).add(newValue);
583+
result.put(key, oldValue);
584+
} else {
585+
ArrayList<Object> l = new ArrayList<>();
586+
l.add(oldValue);
587+
l.add(newValue);
588+
result.put(key, l);
589+
}
590+
}
591+
542592
/**
543593
* Method called to map a JSON Array into a Java Object array (Object[]).
544594
*/
@@ -881,18 +931,72 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
881931
if (key == null) {
882932
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4);
883933
result.put(key1, value1);
884-
result.put(key2, value2);
934+
if (result.put(key2, value2) != null) {
935+
// 22-May-2020, tatu: [databind#2733] may need extra handling
936+
return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
937+
}
885938
return result;
886939
}
887940
// And then the general case; default map size is 16
888941
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
889942
result.put(key1, value1);
890-
result.put(key2, value2);
943+
if (result.put(key2, value2) != null) {
944+
// 22-May-2020, tatu: [databind#2733] may need extra handling
945+
return _mapObjectWithDups(p, ctxt, result, key1, value1, value2, key);
946+
}
947+
891948
do {
892949
p.nextToken();
893-
result.put(key, deserialize(p, ctxt));
950+
final Object newValue = deserialize(p, ctxt);
951+
final Object oldValue = result.put(key, newValue);
952+
if (oldValue != null) {
953+
return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
954+
p.nextFieldName());
955+
}
894956
} while ((key = p.nextFieldName()) != null);
895957
return result;
896958
}
959+
960+
// NOTE: copied from above (alas, no easy way to share/reuse)
961+
// @since 2.12 (wrt [databind#2733]
962+
protected Object _mapObjectWithDups(JsonParser p, DeserializationContext ctxt,
963+
final Map<String, Object> result, String key,
964+
Object oldValue, Object newValue, String nextKey) throws IOException
965+
{
966+
final boolean squashDups = ctxt.isEnabled(StreamReadCapability.DUPLICATE_PROPERTIES);
967+
968+
if (squashDups) {
969+
_squashDups(result, key, oldValue, newValue);
970+
}
971+
972+
while (nextKey != null) {
973+
p.nextToken();
974+
newValue = deserialize(p, ctxt);
975+
oldValue = result.put(nextKey, newValue);
976+
if ((oldValue != null) && squashDups) {
977+
_squashDups(result, key, oldValue, newValue);
978+
}
979+
nextKey = p.nextFieldName();
980+
}
981+
982+
return result;
983+
}
984+
985+
// NOTE: copied from above (alas, no easy way to share/reuse)
986+
@SuppressWarnings("unchecked")
987+
private void _squashDups(final Map<String, Object> result, String key,
988+
Object oldValue, Object newValue)
989+
{
990+
if (oldValue instanceof List<?>) {
991+
((List<Object>) oldValue).add(newValue);
992+
result.put(key, oldValue);
993+
} else {
994+
ArrayList<Object> l = new ArrayList<>();
995+
l.add(oldValue);
996+
l.add(newValue);
997+
result.put(key, l);
998+
}
999+
}
1000+
8971001
}
8981002
}

0 commit comments

Comments
 (0)