Skip to content

Commit 7853276

Browse files
authored
Merge branch '2.19' into 4388-try-another
2 parents 4e04b31 + bdd667e commit 7853276

26 files changed

+456
-51
lines changed

pom.xml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,22 @@
154154
<version>${version.mockito}</version>
155155
<scope>test</scope>
156156
</dependency>
157+
158+
<!-- Dependencies for testing "type pollution", see:
159+
https://github.com/FasterXML/jackson-databind/pull/4848
160+
-->
161+
<dependency>
162+
<groupId>org.junit.platform</groupId>
163+
<artifactId>junit-platform-suite-engine</artifactId>
164+
<version>1.10.2</version>
165+
<scope>test</scope>
166+
</dependency>
167+
<dependency>
168+
<groupId>io.micronaut.test</groupId>
169+
<artifactId>micronaut-test-type-pollution</artifactId>
170+
<version>4.6.2</version>
171+
<scope>test</scope>
172+
</dependency>
157173
</dependencies>
158174

159175
<!-- Alas, need to include snapshot reference since otherwise can not find
@@ -213,6 +229,7 @@
213229
<excludes>
214230
<exclude>com.fasterxml.jackson.databind.MapperFootprintTest</exclude>
215231
</excludes>
232+
<test>com.fasterxml.jackson.databind.PrimarySuite</test>
216233
<!-- 26-Nov-2019, tatu: moar parallelism! Per-class basis, safe, efficient enough
217234
... although not 100% sure this makes much difference TBH
218235
-->
@@ -227,8 +244,8 @@
227244
<artifactId>maven-javadoc-plugin</artifactId>
228245
<configuration>
229246
<links combine.children="append">
230-
<link>https://fasterxml.github.io/jackson-annotations/javadoc/2.16</link>
231-
<link>https://fasterxml.github.io/jackson-core/javadoc/2.16</link>
247+
<link>https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.19.0</link>>
248+
<link>https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.19.0</link>>
232249
</links>
233250
</configuration>
234251
</plugin>
@@ -386,6 +403,19 @@
386403
<configuration>
387404
<argLine>--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED</argLine>
388405
</configuration>
406+
<executions>
407+
<execution>
408+
<id>type-pollution-test</id>
409+
<phase>test</phase>
410+
<goals>
411+
<goal>test</goal>
412+
</goals>
413+
<configuration>
414+
<test>com.fasterxml.jackson.databind.typepollution.TypePollutionSuite</test>
415+
<threadCount>1</threadCount>
416+
</configuration>
417+
</execution>
418+
</executions>
389419
</plugin>
390420
</plugins>
391421
</build>

release-notes/CREDITS-2.x

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ Jonas Konrad (yawkat@github)
425425
* Contributed fix for #3655: `ObjectMapper` default heap consumption increased significantly
426426
from 2.13.x to 2.14.0
427427
(2.14.1)
428+
* Contributed fix for #4848: Avoid type pollution in `StringCollectionDeserializer`
429+
(2.18.3)
428430
429431
Jirka Kremser (Jiri-Kremser@github)
430432
* Suggested #924: SequenceWriter.writeAll() could accept Iterable
@@ -1865,6 +1867,11 @@ Stanislav Shcherbakov (@glorrian)
18651867
* Contributed #4844: Fix wrapped array hanlding wrt `null` by `StdDeserializer`
18661868
(2.18.3)
18671869

1870+
Tomáš Poledný (@Saljack)
1871+
* Reported #4860: `ConstructorDetector.USE_PROPERTIES_BASED` does not work with
1872+
multiple constructors since 2.18
1873+
(2.18.3)
1874+
18681875
Liam Feid (@fxshlein)
18691876
* Contributed #1467: Support `@JsonUnwrapped` with `@JsonCreator`
18701877
(2.19.0)

release-notes/VERSION-2.x

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ Project: jackson-databind
3131
(reported by Eduard G)
3232
#4773: `SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS` should not apply to Maps
3333
with uncomparable keys
34-
(requested by @nathanukey
34+
(requested by @nathanukey)
3535
#4849 Not able to deserialize Enum with default typing after upgrading 2.15.4 -> 2.17.1
3636
(reported by Kornel Zemla)
37+
#4863: Add basic Stream support in `JsonNode`: `valueStream()`, `propertyStream()`,
38+
`forEachEntry()`
3739

3840
2.18.3 (not yet released)
3941

@@ -43,6 +45,12 @@ Project: jackson-databind
4345
(fix by Joo-Hyuk K)
4446
#4844: Fix wrapped array handling wrt `null` by `StdDeserializer`
4547
(fix by Stanislav S)
48+
#4848: Avoid type pollution in `StringCollectionDeserializer`
49+
(contributed by Jonas K)
50+
#4860: `ConstructorDetector.USE_PROPERTIES_BASED` does not work with
51+
multiple constructors since 2.18
52+
(reported by Tomáš P)
53+
(fix by Joo-Hyuk K, @cowtowncoder)
4654

4755
2.18.2 (27-Nov-2024)
4856

src/main/java/com/fasterxml/jackson/databind/JsonNode.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.math.BigDecimal;
55
import java.math.BigInteger;
66
import java.util.*;
7+
import java.util.function.BiConsumer;
8+
import java.util.stream.Stream;
79

810
import com.fasterxml.jackson.core.*;
911
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -1017,9 +1019,14 @@ public Iterator<JsonNode> elements() {
10171019
}
10181020

10191021
/**
1022+
* NOTE: This method is deprecated, use {@link #properties()} instead.
1023+
*
10201024
* @return Iterator that can be used to traverse all key/value pairs for
10211025
* object nodes; empty iterator (no contents) for other types
1026+
*
1027+
* @deprecated As of 2.19, replaced by {@link #properties()}
10221028
*/
1029+
@Deprecated // since 2.19
10231030
public Iterator<Map.Entry<String, JsonNode>> fields() {
10241031
return ClassUtil.emptyIterator();
10251032
}
@@ -1028,6 +1035,7 @@ public Iterator<Map.Entry<String, JsonNode>> fields() {
10281035
* Accessor that will return properties of {@code ObjectNode}
10291036
* similar to how {@link Map#entrySet()} works;
10301037
* for other node types will return empty {@link java.util.Set}.
1038+
* Replacement for {@link JsonNode#fields()}.
10311039
*
10321040
* @return Set of properties, if this node is an {@code ObjectNode}
10331041
* ({@link JsonNode#isObject()} returns {@code true}); empty
@@ -1039,6 +1047,46 @@ public Set<Map.Entry<String, JsonNode>> properties() {
10391047
return Collections.emptySet();
10401048
}
10411049

1050+
/**
1051+
* Returns a stream of all value nodes of this Node, iff
1052+
* this node is an {@code ArrayNode} or {@code ObjectNode}.
1053+
* In case of {@code Object} node, property names (keys) are not included, only values.
1054+
* For other types of nodes, returns empty stream.
1055+
*
1056+
* @since 2.19
1057+
*/
1058+
public Stream<JsonNode> valueStream() {
1059+
return ClassUtil.emptyStream();
1060+
}
1061+
1062+
/**
1063+
* Returns a stream of all properties (key, value pairs) of this Node,
1064+
* iff this node is an an {@code ObjectNode}.
1065+
* For other types of nodes, returns empty stream.
1066+
*
1067+
* @since 2.19
1068+
*/
1069+
public Stream<Map.Entry<String, JsonNode>> propertyStream() {
1070+
return ClassUtil.emptyStream();
1071+
}
1072+
1073+
/**
1074+
* If this node is an {@code ObjectNode}, performs the given action for each
1075+
* property (key, value pair)
1076+
* until all entries have been processed or the action throws an exception.
1077+
* Exceptions thrown by the action are relayed to the caller.
1078+
* For other node types, no action is performed.
1079+
*<p>
1080+
* Actions are performed in the order of properties, same as order returned by
1081+
* method {@link #properties()}.
1082+
* This is generally the document order of properties in JSON object.
1083+
*
1084+
* @param action Action to perform for each entry
1085+
*/
1086+
public void forEachEntry(BiConsumer<? super String, ? super JsonNode> action) {
1087+
// No-op for all but ObjectNode
1088+
}
1089+
10421090
/*
10431091
/**********************************************************
10441092
/* Public API, find methods

src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ private void _addSelectedPropertiesBasedCreator(DeserializationContext ctxt,
534534
if ((name == null) && (injectId == null)) {
535535
ctxt.reportBadTypeDefinition(beanDesc,
536536
"Argument #%d of Creator %s has no property name (and is not Injectable): can not use as property-based Creator",
537-
i, candidate);
537+
i, candidate);
538538
}
539539
}
540540
properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);

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

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.fasterxml.jackson.databind.deser.std;
22

33
import java.io.IOException;
4-
import java.util.Collection;
5-
import java.util.Objects;
4+
import java.util.*;
65

76
import com.fasterxml.jackson.annotation.JsonFormat;
87

9-
import com.fasterxml.jackson.core.*;
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.core.JsonToken;
10+
1011
import com.fasterxml.jackson.databind.*;
1112
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
1213
import com.fasterxml.jackson.databind.cfg.CoercionAction;
@@ -170,16 +171,15 @@ public ValueInstantiator getValueInstantiator() {
170171
/**********************************************************
171172
*/
172173

173-
@SuppressWarnings("unchecked")
174174
@Override
175175
public Collection<String> deserialize(JsonParser p, DeserializationContext ctxt)
176176
throws IOException
177177
{
178178
if (_delegateDeserializer != null) {
179-
return (Collection<String>) _valueInstantiator.createUsingDelegate(ctxt,
180-
_delegateDeserializer.deserialize(p, ctxt));
179+
return castToCollection(_valueInstantiator.createUsingDelegate(ctxt,
180+
_delegateDeserializer.deserialize(p, ctxt)));
181181
}
182-
final Collection<String> result = (Collection<String>) _valueInstantiator.createUsingDefault(ctxt);
182+
final Collection<String> result = castToCollection(_valueInstantiator.createUsingDefault(ctxt));
183183
return deserialize(p, ctxt, result);
184184
}
185185

@@ -272,7 +272,6 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
272272
* throw an exception, or try to handle value as if member of implicit
273273
* array, depending on configuration.
274274
*/
275-
@SuppressWarnings("unchecked")
276275
private final Collection<String> handleNonArray(JsonParser p, DeserializationContext ctxt,
277276
Collection<String> result) throws IOException
278277
{
@@ -284,7 +283,7 @@ private final Collection<String> handleNonArray(JsonParser p, DeserializationCon
284283
if (p.hasToken(JsonToken.VALUE_STRING)) {
285284
return _deserializeFromString(p, ctxt);
286285
}
287-
return (Collection<String>) ctxt.handleUnexpectedToken(_containerType, p);
286+
return castToCollection(ctxt.handleUnexpectedToken(_containerType, p));
288287
}
289288
// Strings are one of "native" (intrinsic) types, so there's never type deserializer involved
290289
JsonDeserializer<String> valueDes = _valueDeserializer;
@@ -306,15 +305,15 @@ private final Collection<String> handleNonArray(JsonParser p, DeserializationCon
306305
final CoercionAction act = ctxt.findCoercionAction(logicalType(), handledType(),
307306
CoercionInputShape.EmptyString);
308307
if (act != CoercionAction.Fail) {
309-
return (Collection<String>) _deserializeFromEmptyString(p, ctxt, act, handledType(),
310-
"empty String (\"\")");
308+
return castToCollection(_deserializeFromEmptyString(p, ctxt, act, handledType(),
309+
"empty String (\"\")"));
311310
}
312311
} else if (_isBlank(textValue)) {
313312
final CoercionAction act = ctxt.findCoercionFromBlankString(logicalType(), handledType(),
314313
CoercionAction.Fail);
315314
if (act != CoercionAction.Fail) {
316-
return (Collection<String>) _deserializeFromEmptyString(p, ctxt, act, handledType(),
317-
"blank String (all whitespace)");
315+
return castToCollection(_deserializeFromEmptyString(p, ctxt, act, handledType(),
316+
"blank String (all whitespace)"));
318317
}
319318
}
320319
// if coercion failed, we can still add it to a list
@@ -329,4 +328,24 @@ private final Collection<String> handleNonArray(JsonParser p, DeserializationCon
329328
result.add(value);
330329
return result;
331330
}
331+
332+
// Used to avoid type pollution: see
333+
// https://micronaut-projects.github.io/micronaut-test/latest/guide/#typePollution
334+
// for details
335+
//
336+
// @since 2.18
337+
@SuppressWarnings("unchecked")
338+
private static Collection<String> castToCollection(Object o) {
339+
if (o != null) {
340+
// fast path for specific classes to avoid type pollution:
341+
// https://micronaut-projects.github.io/micronaut-test/latest/guide/#typePollution
342+
if (o.getClass() == ArrayList.class) {
343+
return (ArrayList<String>) o;
344+
}
345+
if (o.getClass() == HashSet.class) {
346+
return (HashSet<String>) o;
347+
}
348+
}
349+
return (Collection<String>) o;
350+
}
332351
}

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,7 @@ protected void _addCreators(Map<String, POJOPropertyBuilder> props)
713713
}
714714

715715
// One more thing: if neither explicit (constructor or factory) nor
716-
// canonical (constructor?), consider implicit Constructor with
717-
// all named.
716+
// canonical (constructor?), consider implicit Constructor with all named.
718717
final ConstructorDetector ctorDetector = _config.getConstructorDetector();
719718
if (!creators.hasPropertiesBasedOrDelegating()
720719
&& !ctorDetector.requireCtorAnnotation()) {
@@ -1011,10 +1010,17 @@ private boolean _addImplicitConstructor(PotentialCreators collector,
10111010
if (ctorDetector.singleArgCreatorDefaultsToDelegating()) {
10121011
return false;
10131012
}
1013+
// 20-Dec-2024, tatu: [databind#4860] Cannot detect as properties-based
1014+
// without implicit name (Injectable was checked earlier)
1015+
String implicitParamName = ctor.implicitNameSimple(0);
1016+
if (implicitParamName == null) {
1017+
return false;
1018+
}
1019+
10141020
// if not, prefer Properties-based if explicit preference OR
10151021
// property with same name with at least one visible accessor
10161022
if (!ctorDetector.singleArgCreatorDefaultsToProperties()) {
1017-
POJOPropertyBuilder prop = props.get(ctor.implicitNameSimple(0));
1023+
POJOPropertyBuilder prop = props.get(implicitParamName);
10181024
if ((prop == null) || !prop.anyVisible() || prop.anyIgnorals()) {
10191025
return false;
10201026
}

src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.math.BigDecimal;
55
import java.math.BigInteger;
66
import java.util.*;
7+
import java.util.stream.Stream;
78

89
import com.fasterxml.jackson.core.*;
910
import com.fasterxml.jackson.core.type.WritableTypeId;
@@ -288,6 +289,11 @@ public JsonNode required(int index) {
288289
index, _children.size());
289290
}
290291

292+
@Override // @since 2.19
293+
public Stream<JsonNode> valueStream() {
294+
return _children.stream();
295+
}
296+
291297
@Override
292298
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
293299
{

src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.math.BigDecimal;
44
import java.math.BigInteger;
5+
import java.util.stream.Stream;
56

67
import com.fasterxml.jackson.core.*;
78
import com.fasterxml.jackson.databind.JsonNode;
@@ -54,6 +55,10 @@ protected ContainerNode(JsonNodeFactory nc) {
5455
@Override
5556
public abstract JsonNode get(String fieldName);
5657

58+
// Both ArrayNode and ObjectNode must re-implement
59+
@Override // @since 2.19
60+
public abstract Stream<JsonNode> valueStream();
61+
5762
@Override
5863
protected abstract ObjectNode _withObject(JsonPointer origPtr,
5964
JsonPointer currentPtr,

src/main/java/com/fasterxml/jackson/databind/node/InternalNodeMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ protected void _serializeNonRecursive(JsonGenerator g, JsonNode node) throws IOE
9696
{
9797
if (node instanceof ObjectNode) {
9898
g.writeStartObject(this, node.size());
99-
_serializeNonRecursive(g, new IteratorStack(), node.fields());
99+
_serializeNonRecursive(g, new IteratorStack(), node.properties().iterator());
100100
} else if (node instanceof ArrayNode) {
101101
g.writeStartArray(this, node.size());
102102
_serializeNonRecursive(g, new IteratorStack(), node.elements());
@@ -127,7 +127,7 @@ protected void _serializeNonRecursive(JsonGenerator g, IteratorStack stack,
127127
}
128128
if (value instanceof ObjectNode) {
129129
stack.push(currIt);
130-
currIt = value.fields();
130+
currIt = value.properties().iterator();
131131
g.writeStartObject(value, value.size());
132132
} else if (value instanceof ArrayNode) {
133133
stack.push(currIt);

0 commit comments

Comments
 (0)