Skip to content

Commit ea47946

Browse files
committed
Merge branch '2.16'
2 parents f5036d6 + b9ee863 commit ea47946

File tree

6 files changed

+135
-19
lines changed

6 files changed

+135
-19
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Project: jackson-databind
7373
#4090: Support sequenced collections (JDK 21)S
7474
(contributed by @pjfanning)
7575
#4095: Add `withObjectProperty(String)`, `withArrayProperty(String)` in `JsonNode`
76+
#4096: Change `JsonNode.withObject(String)` to work similar to `withArray()`
77+
wrt argument
7678
#4122: Do not resolve wildcards if upper bound is too non-specific
7779
(contributed by @yawkat)
7880

src/main/java/tools/jackson/databind/JsonNode.java

+26-14
Original file line numberDiff line numberDiff line change
@@ -1105,18 +1105,29 @@ public final List<JsonNode> findParents(String fieldName)
11051105
*/
11061106

11071107
/**
1108-
* Short-cut equivalent to:
1108+
* Method that works in one of possible ways, depending on whether
1109+
* {@code exprOrProperty} is a valid {@link JsonPointer} expression or
1110+
* not (valid expression is either empty String {@code ""} or starts
1111+
* with leading slash {@code /} character).
1112+
* If it is, works as a short-cut to:
1113+
*<pre>
1114+
* withObject(JsonPointer.compile(exprOrProperty));
1115+
*</pre>
1116+
* If it is NOT a valid {@link JsonPointer} expression, value is taken
1117+
* as a literal Object property name and calls is alias for
11091118
*<pre>
1110-
* withObject(JsonPointer.compile(expr);
1119+
* withObjectProperty(exprOrProperty);
11111120
*</pre>
1112-
* see {@link #withObject(JsonPointer)} for full explanation.
11131121
*
1114-
* @param expr {@link JsonPointer} expression to use
1122+
* @param exprOrProperty {@link JsonPointer} expression to use (if valid as one),
1123+
* or, if not (no leading "/"), property name to match.
11151124
*
11161125
* @return {@link ObjectNode} found or created
11171126
*/
1118-
public final ObjectNode withObject(String expr) {
1119-
return withObject(JsonPointer.compile(expr));
1127+
public ObjectNode withObject(String exprOrProperty) {
1128+
// To avoid abstract method, base implementation just fails
1129+
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
1130+
+getClass().getName()+")`, cannot call `withObject()` on it");
11201131
}
11211132

11221133
/**
@@ -1215,9 +1226,8 @@ public final ObjectNode withObject(JsonPointer ptr) {
12151226
public ObjectNode withObject(JsonPointer ptr,
12161227
OverwriteMode overwriteMode, boolean preferIndex) {
12171228
// To avoid abstract method, base implementation just fails
1218-
return _reportUnsupportedOperation(
1219-
"`withObject(JsonPointer)` not implemented by `%s`",
1220-
getClass().getName());
1229+
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
1230+
+getClass().getName()+")`, cannot call `withObject()` on it");
12211231
}
12221232

12231233
/**
@@ -1246,17 +1256,19 @@ public ObjectNode withObject(JsonPointer ptr,
12461256
*/
12471257
public ObjectNode withObjectProperty(String propName) {
12481258
// To avoid abstract method, base implementation just fails
1249-
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but `"
1250-
+getClass().getName()+")`, cannot call `withObjectProperty(String)` on it");
1259+
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
1260+
+getClass().getName()+")`, cannot call `withObjectProperty()` on it");
12511261
}
12521262

12531263
/** Short-cut equivalent to:
12541264
*<pre>
12551265
* withArray(JsonPointer.compile(expr), overwriteMode, preferIndex);
12561266
*</pre>
12571267
*/
1258-
public final ArrayNode withArray(String exprOrProperty) {
1259-
return withArray(JsonPointer.compile(exprOrProperty));
1268+
public ArrayNode withArray(String exprOrProperty) {
1269+
// To avoid abstract method, base implementation just fails
1270+
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
1271+
+getClass().getName()+")`, cannot call `withArray()` on it");
12601272
}
12611273

12621274
/**
@@ -1375,7 +1387,7 @@ public ArrayNode withArray(JsonPointer ptr,
13751387
*/
13761388
public ArrayNode withArrayProperty(String propName) {
13771389
// To avoid abstract method, base implementation just fails
1378-
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but `"
1390+
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
13791391
+getClass().getName()+")`, cannot call `withArrayProperty(String)` on it");
13801392
}
13811393

src/main/java/tools/jackson/databind/node/BaseJsonNode.java

+7
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,11 @@ protected BigInteger _bigIntFromBigDec(BigDecimal value) {
274274
StreamReadConstraints.defaults().validateBigIntegerScale(value.scale());
275275
return value.toBigInteger();
276276
}
277+
278+
protected JsonPointer _jsonPointerIfValid(String exprOrProperty) {
279+
if (exprOrProperty.isEmpty() || exprOrProperty.charAt(0) == '/') {
280+
return JsonPointer.compile(exprOrProperty);
281+
}
282+
return null;
283+
}
277284
}

src/main/java/tools/jackson/databind/node/ObjectNode.java

+18
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ public ObjectNode deepCopy()
6363
/**********************************************************************
6464
*/
6565

66+
@Override
67+
public ObjectNode withObject(String exprOrProperty) {
68+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
69+
if (ptr != null) {
70+
return withObject(ptr);
71+
}
72+
return withObjectProperty(exprOrProperty);
73+
}
74+
6675
@Override
6776
public ObjectNode withObjectProperty(String propName) {
6877
JsonNode child = _children.get(propName);
@@ -77,6 +86,15 @@ public ObjectNode withObjectProperty(String propName) {
7786
child.getClass().getName(), propName, OverwriteMode.NULLS);
7887
}
7988

89+
@Override
90+
public ArrayNode withArray(String exprOrProperty) {
91+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
92+
if (ptr != null) {
93+
return withArray(ptr);
94+
}
95+
return withArrayProperty(exprOrProperty);
96+
}
97+
8098
@Override
8199
public ArrayNode withArrayProperty(String propName) {
82100
JsonNode child = _children.get(propName);

src/test/java/tools/jackson/databind/node/ObjectNodeTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ public void testInvalidWithObject() throws Exception
320320
root.withObject("/prop");
321321
fail("Expected exception");
322322
} catch (JsonNodeException e) {
323-
verifyException(e, "Cannot replace context node (of type");
323+
verifyException(e, "`JsonNode` not of type `ObjectNode`");
324324
verifyException(e, "ArrayNode");
325325
}
326326
// also: should fail of we already have non-object property
@@ -342,7 +342,7 @@ public void testInvalidWithArray() throws Exception
342342
root.withArray("/prop");
343343
fail("Expected exception");
344344
} catch (JsonNodeException e) {
345-
verifyException(e, "Cannot replace context node (of type");
345+
verifyException(e, "`JsonNode` not of type `ObjectNode`");
346346
verifyException(e, "ArrayNode");
347347
}
348348
// also: should fail of we already have non-Array property

src/test/java/tools/jackson/databind/node/WithPathTest.java

+80-3
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,11 @@ private void _verifyObjectReplaceFail(JsonNode doc, JsonPointer ptr, OverwriteMo
200200

201201
/*
202202
/**********************************************************************
203-
/* Test methods, withObjectProperty()
203+
/* Test methods, withObjectProperty()/withObject(exprOrProperty)
204204
/**********************************************************************
205205
*/
206206

207+
// [databind#4095]
207208
public void testWithObjectProperty() throws Exception
208209
{
209210
ObjectNode root = MAPPER.createObjectNode();
@@ -228,7 +229,7 @@ public void testWithObjectProperty() throws Exception
228229
ObjectNode match3 = root2.withObjectProperty("b");
229230
assertNotSame(match, match3);
230231
assertEquals("{\"b\":{}}", root2.toString());
231-
232+
232233
// and then failing case
233234
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
234235
try {
@@ -239,6 +240,46 @@ public void testWithObjectProperty() throws Exception
239240
}
240241
}
241242

243+
// [databind#4096]
244+
public void testWithObjectAdnExprOrProp() throws Exception
245+
{
246+
ObjectNode root = MAPPER.createObjectNode();
247+
248+
// First: create new property value
249+
ObjectNode match = root.withObject("a");
250+
assertTrue(match.isObject());
251+
assertEquals(a2q("{}"), match.toString());
252+
match.put("value", 42);
253+
assertEquals(a2q("{'a':{'value':42}}"), root.toString());
254+
255+
// and then with JsonPointer expr
256+
match = root.withObject("/a/b");
257+
assertTrue(match.isObject());
258+
assertEquals(a2q("{}"), match.toString());
259+
assertEquals(a2q("{'a':{'value':42,'b':{}}}"), root.toString());
260+
261+
// Then existing prop:
262+
assertEquals(a2q("{'value':42,'b':{}}"),
263+
root.withObject("a").toString());
264+
assertEquals(a2q("{}"),
265+
root.withObject("/a/b").toString());
266+
267+
// and then failing case
268+
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
269+
try {
270+
root3.withObject("c");
271+
fail("Should not pass");
272+
} catch (JsonNodeException e) {
273+
verifyException(e, "Cannot replace `JsonNode` of type ");
274+
}
275+
try {
276+
root3.withObject("/c");
277+
fail("Should not pass");
278+
} catch (JsonNodeException e) {
279+
verifyException(e, "Cannot replace `JsonNode` of type ");
280+
}
281+
}
282+
242283
/*
243284
/**********************************************************************
244285
/* Test methods, withArray()
@@ -361,10 +402,11 @@ public void testWithArray3882() throws Exception
361402

362403
/*
363404
/**********************************************************************
364-
/* Test methods, withArrayProperty()
405+
/* Test methods, withArrayProperty()/withArray(exprOrProperty)
365406
/**********************************************************************
366407
*/
367408

409+
// [databind#4095]
368410
public void testWithArrayProperty() throws Exception
369411
{
370412
ObjectNode root = MAPPER.createObjectNode();
@@ -398,4 +440,39 @@ public void testWithArrayProperty() throws Exception
398440
verifyException(e, "Cannot replace `JsonNode` of type ");
399441
}
400442
}
443+
444+
// [databind#4096]
445+
public void testWithArrayAndExprOrProp() throws Exception
446+
{
447+
ObjectNode root = MAPPER.createObjectNode();
448+
449+
// First: create new property value
450+
ArrayNode match = root.withArray("a");
451+
assertTrue(match.isArray());
452+
assertEquals(a2q("[]"), match.toString());
453+
match.add(42);
454+
assertEquals(a2q("{'a':[42]}"), root.toString());
455+
456+
match = root.withArray("/b");
457+
assertEquals(a2q("{'a':[42],'b':[]}"), root.toString());
458+
459+
// Second: match existing Object property
460+
assertEquals(a2q("[42]"), root.withArray("a").toString());
461+
assertEquals(a2q("[42]"), root.withArray("/a").toString());
462+
463+
// and then failing case
464+
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
465+
try {
466+
root3.withArray("c");
467+
fail("Should not pass");
468+
} catch (JsonNodeException e) {
469+
verifyException(e, "Cannot replace `JsonNode` of type ");
470+
}
471+
try {
472+
root3.withArray("/c");
473+
fail("Should not pass: resulting doc = "+root3);
474+
} catch (JsonNodeException e) {
475+
verifyException(e, "Cannot replace `JsonNode` of type ");
476+
}
477+
}
401478
}

0 commit comments

Comments
 (0)