Skip to content

Commit b9ee863

Browse files
authored
Fix #4096: change JsonNode.withObject(String) to accept non-expression as property name (#4132)
1 parent 042cd3d commit b9ee863

File tree

5 files changed

+104
-11
lines changed

5 files changed

+104
-11
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/com/fasterxml/jackson/databind/JsonNode.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -1139,20 +1139,31 @@ public final List<JsonNode> findParents(String fieldName)
11391139
*/
11401140

11411141
/**
1142-
* Short-cut equivalent to:
1142+
* Method that works in one of possible ways, depending on whether
1143+
* {@code exprOrProperty} is a valid {@link JsonPointer} expression or
1144+
* not (valid expression is either empty String {@code ""} or starts
1145+
* with leading slash {@code /} character).
1146+
* If it is, works as a short-cut to:
11431147
*<pre>
1144-
* withObject(JsonPointer.compile(expr);
1148+
* withObject(JsonPointer.compile(exprOrProperty));
1149+
*</pre>
1150+
* If it is NOT a valid {@link JsonPointer} expression, value is taken
1151+
* as a literal Object property name and calls is alias for
1152+
*<pre>
1153+
* withObjectProperty(exprOrProperty);
11451154
*</pre>
1146-
* see {@link #withObject(JsonPointer)} for full explanation.
11471155
*
1148-
* @param expr {@link JsonPointer} expression to use
1156+
* @param exprOrProperty {@link JsonPointer} expression to use (if valid as one),
1157+
* or, if not (no leading "/"), property name to match.
11491158
*
11501159
* @return {@link ObjectNode} found or created
11511160
*
1152-
* @since 2.14
1161+
* @since 2.14 (but semantics before 2.16 did NOT allow for non-JsonPointer expressions)
11531162
*/
1154-
public final ObjectNode withObject(String expr) {
1155-
return withObject(JsonPointer.compile(expr));
1163+
public ObjectNode withObject(String exprOrProperty) {
1164+
// To avoid abstract method, base implementation just fails
1165+
throw new UnsupportedOperationException("`withObject(String)` not implemented by `"
1166+
+getClass().getName()+"`");
11561167
}
11571168

11581169
/**

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

+9
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ public ObjectNode with(String exprOrProperty) {
8989
return result;
9090
}
9191

92+
@Override
93+
public ObjectNode withObject(String exprOrProperty) {
94+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
95+
if (ptr != null) {
96+
return withObject(ptr);
97+
}
98+
return withObjectProperty(exprOrProperty);
99+
}
100+
92101
@Override
93102
public ObjectNode withObjectProperty(String propName) {
94103
JsonNode child = _children.get(propName);

src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public void testInvalidWithObject() throws Exception
318318
root.withObject("/prop");
319319
fail("Expected exception");
320320
} catch (UnsupportedOperationException e) {
321-
verifyException(e, "Cannot replace context node (of type");
321+
verifyException(e, "`withObject(String)` not implemented");
322322
verifyException(e, "ArrayNode");
323323
}
324324
// also: should fail of we already have non-object property

src/test/java/com/fasterxml/jackson/databind/node/WithPathTest.java

+74-3
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,11 @@ private void _verifyObjectReplaceFail(JsonNode doc, JsonPointer ptr, OverwriteMo
198198

199199
/*
200200
/**********************************************************************
201-
/* Test methods, withObjectProperty()
201+
/* Test methods, withObjectProperty()/withObject(exprOrProperty)
202202
/**********************************************************************
203203
*/
204204

205+
// [databind#4095]
205206
public void testWithObjectProperty() throws Exception
206207
{
207208
ObjectNode root = MAPPER.createObjectNode();
@@ -226,7 +227,7 @@ public void testWithObjectProperty() throws Exception
226227
ObjectNode match3 = root2.withObjectProperty("b");
227228
assertNotSame(match, match3);
228229
assertEquals("{\"b\":{}}", root2.toString());
229-
230+
230231
// and then failing case
231232
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
232233
try {
@@ -237,6 +238,46 @@ public void testWithObjectProperty() throws Exception
237238
}
238239
}
239240

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

360401
/*
361402
/**********************************************************************
362-
/* Test methods, withArrayProperty()
403+
/* Test methods, withArrayProperty()/withArray(exprOrProperty)
363404
/**********************************************************************
364405
*/
365406

407+
// [databind#4095]
366408
public void testWithArrayProperty() throws Exception
367409
{
368410
ObjectNode root = MAPPER.createObjectNode();
@@ -396,4 +438,33 @@ public void testWithArrayProperty() throws Exception
396438
verifyException(e, "Cannot replace `JsonNode` of type ");
397439
}
398440
}
441+
442+
// [databind#4096]
443+
public void testWithArrayAndExprOrProp() throws Exception
444+
{
445+
ObjectNode root = MAPPER.createObjectNode();
446+
447+
// First: create new property value
448+
ArrayNode match = root.withArray("a");
449+
assertTrue(match.isArray());
450+
assertEquals(a2q("[]"), match.toString());
451+
match.add(42);
452+
assertEquals(a2q("{'a':[42]}"), root.toString());
453+
454+
match = root.withArray("/b");
455+
assertEquals(a2q("{'a':[42],'b':[]}"), root.toString());
456+
457+
// Second: match existing Object property
458+
assertEquals(a2q("[42]"), root.withArray("a").toString());
459+
assertEquals(a2q("[42]"), root.withArray("/a").toString());
460+
461+
// and then failing case
462+
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
463+
try {
464+
root3.withArrayProperty("c");
465+
fail("Should not pass");
466+
} catch (UnsupportedOperationException e) {
467+
verifyException(e, "Cannot replace `JsonNode` of type ");
468+
}
469+
}
399470
}

0 commit comments

Comments
 (0)