Skip to content

Commit bc9d37e

Browse files
authored
Merge branch '2.x' into feature/databind-1381-inject-only
2 parents 5705aff + 961892d commit bc9d37e

File tree

7 files changed

+230
-48
lines changed

7 files changed

+230
-48
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
java_version: ['8', '11', '17', '21', '23']
22+
java_version: ['8', '11', '17', '21', '24']
2323
os: ['ubuntu-24.04']
2424
include:
2525
- java_version: '8'

release-notes/VERSION-2.x

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Project: jackson-databind
2727
#5179: Add "current token" info into `MismatchedInputException`
2828
- Generate SBOMs [JSTEP-14]
2929
30-
2.19.1 (not yet released)
30+
2.19.1 (13-Jun-2025)
3131
3232
#5139: In `CollectionDeserializer`, `JsonSetter.contentNulls` is sometimes ignored
3333
(contributed by @wrongwrong)

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,6 @@ public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt,
269269
// [databind#631]: Assign current value, to be accessible by custom deserializers
270270
p.assignCurrentValue(result);
271271

272-
final JsonDeserializer<Object> valueDes = _valueDeserializer;
273-
final TypeDeserializer typeDeser = _valueTypeDeserializer;
274-
275272
String keyStr;
276273
if (p.isExpectedStartObjectToken()) {
277274
keyStr = p.nextFieldName();
@@ -312,10 +309,8 @@ public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt,
312309
continue;
313310
}
314311
value = _nullProvider.getNullValue(ctxt);
315-
} else if (typeDeser == null) {
316-
value = valueDes.deserialize(p, ctxt);
317312
} else {
318-
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
313+
value = _deserializeNoNullChecks(p, ctxt);
319314
}
320315
} catch (Exception e) {
321316
return wrapAndThrow(ctxt, e, result, keyStr);
@@ -406,10 +401,8 @@ public EnumMap<?,?> _deserializeUsingProperties(JsonParser p, DeserializationCon
406401
continue;
407402
}
408403
value = _nullProvider.getNullValue(ctxt);
409-
} else if (_valueTypeDeserializer == null) {
410-
value = _valueDeserializer.deserialize(p, ctxt);
411404
} else {
412-
value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
405+
value = _deserializeNoNullChecks(p, ctxt);
413406
}
414407
} catch (Exception e) {
415408
wrapAndThrow(ctxt, e, _containerType.getRawClass(), keyName);
@@ -426,4 +419,20 @@ public EnumMap<?,?> _deserializeUsingProperties(JsonParser p, DeserializationCon
426419
return null;
427420
}
428421
}
422+
423+
/**
424+
* Deserialize the content of the map.
425+
* If _valueTypeDeserializer is null, use _valueDeserializer.deserialize; if non-null,
426+
* use _valueDeserializer.deserializeWithType to deserialize value.
427+
* This method only performs deserialization and does not consider _skipNullValues, _nullProvider, etc.
428+
* @since 2.19.2
429+
*/
430+
protected Object _deserializeNoNullChecks(JsonParser p, DeserializationContext ctxt)
431+
throws IOException
432+
{
433+
if (_valueTypeDeserializer == null) {
434+
return _valueDeserializer.deserialize(p, ctxt);
435+
}
436+
return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
437+
}
429438
}

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

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,11 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
343343
if (ignorals != null) {
344344
Set<String> ignoresToAdd = ignorals.findIgnoredForDeserialization();
345345
if (!ignoresToAdd.isEmpty()) {
346-
ignored = (ignored == null) ? new HashSet<String>() : new HashSet<String>(ignored);
347-
for (String str : ignoresToAdd) {
348-
ignored.add(str);
346+
if (ignored == null) {
347+
ignored = new HashSet<>(ignoresToAdd);
348+
} else {
349+
ignored = new HashSet<String>(ignored);
350+
ignored.addAll(ignoresToAdd);
349351
}
350352
}
351353
}
@@ -500,8 +502,6 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
500502
@SuppressWarnings("unchecked")
501503
public final Class<?> getMapClass() { return (Class<Map<Object,Object>>) _containerType.getRawClass(); }
502504

503-
@Override public JavaType getValueType() { return _containerType; }
504-
505505
/*
506506
/**********************************************************
507507
/* Internal methods, non-merging deserialization
@@ -511,12 +511,8 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
511511
protected final Map<Object,Object> _readAndBind(JsonParser p, DeserializationContext ctxt,
512512
Map<Object,Object> result) throws IOException
513513
{
514-
final KeyDeserializer keyDes = _keyDeserializer;
515-
final JsonDeserializer<Object> valueDes = _valueDeserializer;
516-
final TypeDeserializer typeDeser = _valueTypeDeserializer;
517-
518514
MapReferringAccumulator referringAccumulator = null;
519-
boolean useObjectId = valueDes.getObjectIdReader() != null;
515+
boolean useObjectId = _valueDeserializer.getObjectIdReader() != null;
520516
if (useObjectId) {
521517
referringAccumulator = new MapReferringAccumulator(_containerType.getContentType().getRawClass(),
522518
result);
@@ -537,7 +533,7 @@ protected final Map<Object,Object> _readAndBind(JsonParser p, DeserializationCon
537533
}
538534

539535
for (; keyStr != null; keyStr = p.nextFieldName()) {
540-
Object key = keyDes.deserializeKey(keyStr, ctxt);
536+
Object key = _keyDeserializer.deserializeKey(keyStr, ctxt);
541537
// And then the value...
542538
JsonToken t = p.nextToken();
543539
if ((_inclusionChecker != null) && _inclusionChecker.shouldIgnore(keyStr)) {
@@ -552,10 +548,8 @@ protected final Map<Object,Object> _readAndBind(JsonParser p, DeserializationCon
552548
continue;
553549
}
554550
value = _nullProvider.getNullValue(ctxt);
555-
} else if (typeDeser == null) {
556-
value = valueDes.deserialize(p, ctxt);
557551
} else {
558-
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
552+
value = _deserializeNoNullChecks(p, ctxt);
559553
}
560554
if (useObjectId) {
561555
referringAccumulator.put(key, value);
@@ -582,10 +576,8 @@ protected final Map<Object,Object> _readAndBind(JsonParser p, DeserializationCon
582576
protected final Map<Object,Object> _readAndBindStringKeyMap(JsonParser p, DeserializationContext ctxt,
583577
Map<Object,Object> result) throws IOException
584578
{
585-
final JsonDeserializer<Object> valueDes = _valueDeserializer;
586-
final TypeDeserializer typeDeser = _valueTypeDeserializer;
587579
MapReferringAccumulator referringAccumulator = null;
588-
boolean useObjectId = (valueDes.getObjectIdReader() != null);
580+
boolean useObjectId = (_valueDeserializer.getObjectIdReader() != null);
589581
if (useObjectId) {
590582
referringAccumulator = new MapReferringAccumulator(_containerType.getContentType().getRawClass(), result);
591583
}
@@ -618,10 +610,8 @@ protected final Map<Object,Object> _readAndBindStringKeyMap(JsonParser p, Deseri
618610
continue;
619611
}
620612
value = _nullProvider.getNullValue(ctxt);
621-
} else if (typeDeser == null) {
622-
value = valueDes.deserialize(p, ctxt);
623613
} else {
624-
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
614+
value = _deserializeNoNullChecks(p, ctxt);
625615
}
626616
if (useObjectId) {
627617
referringAccumulator.put(key, value);
@@ -649,9 +639,6 @@ public Map<Object,Object> _deserializeUsingCreator(JsonParser p, Deserialization
649639
// null -> no ObjectIdReader for Maps (yet?)
650640
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, null);
651641

652-
final JsonDeserializer<Object> valueDes = _valueDeserializer;
653-
final TypeDeserializer typeDeser = _valueTypeDeserializer;
654-
655642
String key;
656643
if (p.isExpectedStartObjectToken()) {
657644
key = p.nextFieldName();
@@ -693,10 +680,8 @@ public Map<Object,Object> _deserializeUsingCreator(JsonParser p, Deserialization
693680
continue;
694681
}
695682
value = _nullProvider.getNullValue(ctxt);
696-
} else if (typeDeser == null) {
697-
value = valueDes.deserialize(p, ctxt);
698683
} else {
699-
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
684+
value = _deserializeNoNullChecks(p, ctxt);
700685
}
701686
} catch (Exception e) {
702687
wrapAndThrow(ctxt, e, _containerType.getRawClass(), key);
@@ -726,7 +711,6 @@ public Map<Object,Object> _deserializeUsingCreator(JsonParser p, Deserialization
726711
protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt,
727712
Map<Object,Object> result) throws IOException
728713
{
729-
final KeyDeserializer keyDes = _keyDeserializer;
730714
final JsonDeserializer<Object> valueDes = _valueDeserializer;
731715
final TypeDeserializer typeDeser = _valueTypeDeserializer;
732716

@@ -748,7 +732,7 @@ protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt,
748732
}
749733

750734
for (; keyStr != null; keyStr = p.nextFieldName()) {
751-
Object key = keyDes.deserializeKey(keyStr, ctxt);
735+
Object key = _keyDeserializer.deserializeKey(keyStr, ctxt);
752736
// And then the value...
753737
JsonToken t = p.nextToken();
754738
if ((_inclusionChecker != null) && _inclusionChecker.shouldIgnore(keyStr)) {
@@ -772,10 +756,8 @@ protected final void _readAndUpdate(JsonParser p, DeserializationContext ctxt,
772756
} else {
773757
value = valueDes.deserializeWithType(p, ctxt, typeDeser, old);
774758
}
775-
} else if (typeDeser == null) {
776-
value = valueDes.deserialize(p, ctxt);
777759
} else {
778-
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
760+
value = _deserializeNoNullChecks(p, ctxt);
779761
}
780762
if (value != old) {
781763
result.put(key, value);
@@ -839,10 +821,8 @@ protected final void _readAndUpdateStringKeyMap(JsonParser p, DeserializationCon
839821
} else {
840822
value = valueDes.deserializeWithType(p, ctxt, typeDeser, old);
841823
}
842-
} else if (typeDeser == null) {
843-
value = valueDes.deserialize(p, ctxt);
844824
} else {
845-
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
825+
value = _deserializeNoNullChecks(p, ctxt);
846826
}
847827
if (value != old) {
848828
result.put(key, value);
@@ -853,6 +833,22 @@ protected final void _readAndUpdateStringKeyMap(JsonParser p, DeserializationCon
853833
}
854834
}
855835

836+
/**
837+
* Deserialize the content of the map.
838+
* If _valueTypeDeserializer is null, use _valueDeserializer.deserialize; if non-null,
839+
* use _valueDeserializer.deserializeWithType to deserialize value.
840+
* This method only performs deserialization and does not consider _skipNullValues, _nullProvider, etc.
841+
* @since 2.19.2
842+
*/
843+
protected Object _deserializeNoNullChecks(JsonParser p, DeserializationContext ctxt)
844+
throws IOException
845+
{
846+
if (_valueTypeDeserializer == null) {
847+
return _valueDeserializer.deserialize(p, ctxt);
848+
}
849+
return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
850+
}
851+
856852
/**
857853
* @since 2.14
858854
*/
@@ -895,11 +891,11 @@ private void handleUnresolvedReference(DeserializationContext ctxt,
895891

896892
private final static class MapReferringAccumulator {
897893
private final Class<?> _valueType;
898-
private Map<Object,Object> _result;
894+
private final Map<Object,Object> _result;
899895
/**
900896
* A list of {@link MapReferring} to maintain ordering.
901897
*/
902-
private List<MapReferring> _accumulator = new ArrayList<MapReferring>();
898+
private final List<MapReferring> _accumulator = new ArrayList<MapReferring>();
903899

904900
public MapReferringAccumulator(Class<?> valueType, Map<Object, Object> result) {
905901
_valueType = valueType;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.fasterxml.jackson.databind.tofix;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
8+
import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
12+
// [databind#5115] @JsonUnwrapped can't handle name collision #5115
13+
public class RecordUnwrapped5115Test
14+
extends DatabindTestUtil
15+
{
16+
record FooRecord5115(int a, int b) { }
17+
record BarRecordFail5115(@JsonUnwrapped FooRecord5115 a, int c) { }
18+
record BarRecordPass5115(@JsonUnwrapped FooRecord5115 foo, int c) { }
19+
20+
static class FooPojo5115 {
21+
public int a;
22+
public int b;
23+
}
24+
25+
static class BarPojo5115 {
26+
@JsonUnwrapped
27+
public FooPojo5115 a;
28+
public int c;
29+
}
30+
31+
private final ObjectMapper MAPPER = newJsonMapper();
32+
33+
@Test
34+
void unwrappedPojoShouldRoundTrip() throws Exception
35+
{
36+
BarPojo5115 input = new BarPojo5115();
37+
input.a = new FooPojo5115();
38+
input.c = 4;
39+
input.a.a = 1;
40+
input.a.b = 2;
41+
42+
String json = MAPPER.writeValueAsString(input);
43+
BarPojo5115 output = MAPPER.readValue(json, BarPojo5115.class);
44+
45+
assertEquals(4, output.c);
46+
assertEquals(1, output.a.a);
47+
assertEquals(2, output.a.b);
48+
}
49+
50+
@Test
51+
void unwrappedRecordShouldRoundTripPass() throws Exception
52+
{
53+
BarRecordPass5115 input = new BarRecordPass5115(new FooRecord5115(1, 2), 3);
54+
55+
// Serialize
56+
String json = MAPPER.writeValueAsString(input);
57+
58+
// Deserialize (currently fails)
59+
BarRecordPass5115 output = MAPPER.readValue(json, BarRecordPass5115.class);
60+
61+
// Should match after bug is fixed
62+
assertEquals(input, output);
63+
}
64+
65+
@JacksonTestFailureExpected
66+
@Test
67+
void unwrappedRecordShouldRoundTrip() throws Exception
68+
{
69+
BarRecordFail5115 input = new BarRecordFail5115(new FooRecord5115(1, 2), 3);
70+
71+
// Serialize
72+
String json = MAPPER.writeValueAsString(input);
73+
74+
// Once the bug is fixed, this assertion will pass and the
75+
// @JacksonTestFailureExpected annotation can be removed.
76+
BarRecordFail5115 output = MAPPER.readValue(json, BarRecordFail5115.class);
77+
78+
// Should match after bug is fixed
79+
assertEquals(input, output);
80+
}
81+
82+
}

0 commit comments

Comments
 (0)