Skip to content

Commit f5036d6

Browse files
committed
Merge branch '2.16'
2 parents 723f706 + 042cd3d commit f5036d6

File tree

5 files changed

+169
-2
lines changed

5 files changed

+169
-2
lines changed

release-notes/VERSION-2.x

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Project: jackson-databind
7272
trying to setAccessible on `OptionalInt` with JDK 17+
7373
#4090: Support sequenced collections (JDK 21)S
7474
(contributed by @pjfanning)
75+
#4095: Add `withObjectProperty(String)`, `withArrayProperty(String)` in `JsonNode`
7576
#4122: Do not resolve wildcards if upper bound is too non-specific
7677
(contributed by @yawkat)
7778

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,36 @@ public ObjectNode withObject(JsonPointer ptr,
12211221
}
12221222

12231223
/**
1224-
* Short-cut equivalent to:
1224+
* Method similar to {@link #withObject(JsonPointer, OverwriteMode, boolean)} -- basically
1225+
* short-cut to:
1226+
*<pre>
1227+
* withObject(JsonPointer.compile("/"+propName), OverwriteMode.NULLS, false);
1228+
*</pre>
1229+
* that is, only matches immediate property on {@link ObjectNode}
1230+
* and will either use an existing {@link ObjectNode} that is
1231+
* value of the property, or create one if no value or value is {@code NullNode}.
1232+
* <br>
1233+
* Will fail with an exception if:
1234+
* <ul>
1235+
* <li>Node method called on is NOT {@link ObjectNode}
1236+
* </li>
1237+
* <li>Property has an existing value that is NOT {@code NullNode} (explicit {@code null})
1238+
* </li>
1239+
* </ul>
1240+
*
1241+
* @param propName Name of property that has or will have {@link ObjectNode} as value
1242+
*
1243+
* @return {@link ObjectNode} value of given property (existing or created)
1244+
*
1245+
* @since 2.16
1246+
*/
1247+
public ObjectNode withObjectProperty(String propName) {
1248+
// 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");
1251+
}
1252+
1253+
/** Short-cut equivalent to:
12251254
*<pre>
12261255
* withArray(JsonPointer.compile(expr), overwriteMode, preferIndex);
12271256
*</pre>
@@ -1322,6 +1351,34 @@ public ArrayNode withArray(JsonPointer ptr,
13221351
ClassUtil.nameOf(getClass()));
13231352
}
13241353

1354+
/**
1355+
* Method similar to {@link #withArray(JsonPointer, OverwriteMode, boolean)} -- basically
1356+
* short-cut to:
1357+
*<pre>
1358+
* withArray(JsonPointer.compile("/"+propName), OverwriteMode.NULLS, false);
1359+
*</pre>
1360+
* that is, only matches immediate property on {@link ObjectNode}
1361+
* and will either use an existing {@link ArrayNode} that is
1362+
* value of the property, or create one if no value or value is {@code NullNode}.
1363+
* <br>
1364+
* Will fail with an exception if:
1365+
* <ul>
1366+
* <li>Node method called on is NOT {@link ObjectNode}
1367+
* </li>
1368+
* <li>Property has an existing value that is NOT {@code NullNode} (explicit {@code null})
1369+
* </li>
1370+
* </ul>
1371+
*
1372+
* @param propName Name of property that has or will have {@link ArrayNode} as value
1373+
*
1374+
* @return {@link ArrayNode} value of given property (existing or created)
1375+
*/
1376+
public ArrayNode withArrayProperty(String propName) {
1377+
// To avoid abstract method, base implementation just fails
1378+
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but `"
1379+
+getClass().getName()+")`, cannot call `withArrayProperty(String)` on it");
1380+
}
1381+
13251382
/*
13261383
/**********************************************************************
13271384
/* Public API, comparison

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ protected boolean _withXxxMayReplace(JsonNode node, OverwriteMode overwriteMode)
192192
public ArrayNode withArray(JsonPointer ptr,
193193
OverwriteMode overwriteMode, boolean preferIndex)
194194
{
195-
// Degenerate case of using with "empty" path; ok if ObjectNode
195+
// Degenerate case of using with "empty" path; ok if ArrayNode
196196
if (ptr.matches()) {
197197
if (this instanceof ArrayNode) {
198198
return (ArrayNode) this;

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,34 @@ public ObjectNode deepCopy()
6363
/**********************************************************************
6464
*/
6565

66+
@Override
67+
public ObjectNode withObjectProperty(String propName) {
68+
JsonNode child = _children.get(propName);
69+
if (child == null || child.isNull()) {
70+
return putObject(propName);
71+
}
72+
if (child.isObject()) {
73+
return (ObjectNode) child;
74+
}
75+
return _reportWrongNodeType(
76+
"Cannot replace `JsonNode` of type `%s` with `ObjectNode` for property \"%s\" (default mode `OverwriteMode.%s`)",
77+
child.getClass().getName(), propName, OverwriteMode.NULLS);
78+
}
79+
80+
@Override
81+
public ArrayNode withArrayProperty(String propName) {
82+
JsonNode child = _children.get(propName);
83+
if (child == null || child.isNull()) {
84+
return putArray(propName);
85+
}
86+
if (child.isArray()) {
87+
return (ArrayNode) child;
88+
}
89+
return _reportWrongNodeType(
90+
"Cannot replace `JsonNode` of type `%s` with `ArrayNode` for property \"%s\" with (default mode `OverwriteMode.%s`)",
91+
child.getClass().getName(), propName, OverwriteMode.NULLS);
92+
}
93+
6694
@Override
6795
protected ObjectNode _withObject(JsonPointer origPtr,
6896
JsonPointer currentPtr,

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,47 @@ private void _verifyObjectReplaceFail(JsonNode doc, JsonPointer ptr, OverwriteMo
198198
}
199199
}
200200

201+
/*
202+
/**********************************************************************
203+
/* Test methods, withObjectProperty()
204+
/**********************************************************************
205+
*/
206+
207+
public void testWithObjectProperty() throws Exception
208+
{
209+
ObjectNode root = MAPPER.createObjectNode();
210+
211+
// First: create new property value
212+
ObjectNode match = root.withObjectProperty("a");
213+
assertTrue(match.isObject());
214+
assertEquals(a2q("{}"), match.toString());
215+
match.put("value", 42);
216+
assertEquals(a2q("{'a':{'value':42}}"), root.toString());
217+
218+
// Second: match existing Object property
219+
ObjectNode match2 = root.withObjectProperty("a");
220+
assertSame(match, match2);
221+
match.put("value2", true);
222+
223+
assertEquals(a2q("{'a':{'value':42,'value2':true}}"),
224+
root.toString());
225+
226+
// Third: match and overwrite existing null node
227+
JsonNode root2 = MAPPER.readTree("{\"b\": null}");
228+
ObjectNode match3 = root2.withObjectProperty("b");
229+
assertNotSame(match, match3);
230+
assertEquals("{\"b\":{}}", root2.toString());
231+
232+
// and then failing case
233+
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
234+
try {
235+
root3.withObjectProperty("c");
236+
fail("Should not pass");
237+
} catch (JsonNodeException e) {
238+
verifyException(e, "Cannot replace `JsonNode` of type ");
239+
}
240+
}
241+
201242
/*
202243
/**********************************************************************
203244
/* Test methods, withArray()
@@ -317,4 +358,44 @@ public void testWithArray3882() throws Exception
317358
assertEquals(a2q("{'key1':{'array1':[{'element1':['v1']}]}}"),
318359
root.toString());
319360
}
361+
362+
/*
363+
/**********************************************************************
364+
/* Test methods, withArrayProperty()
365+
/**********************************************************************
366+
*/
367+
368+
public void testWithArrayProperty() throws Exception
369+
{
370+
ObjectNode root = MAPPER.createObjectNode();
371+
372+
// First: create new property value
373+
ArrayNode match = root.withArrayProperty("a");
374+
assertTrue(match.isArray());
375+
assertEquals(a2q("[]"), match.toString());
376+
match.add(42);
377+
assertEquals(a2q("{'a':[42]}"), root.toString());
378+
379+
// Second: match existing Object property
380+
ArrayNode match2 = root.withArrayProperty("a");
381+
assertSame(match, match2);
382+
match.add(true);
383+
384+
assertEquals(a2q("{'a':[42,true]}"), root.toString());
385+
386+
// Third: match and overwrite existing null node
387+
JsonNode root2 = MAPPER.readTree("{\"b\": null}");
388+
ArrayNode match3 = root2.withArrayProperty("b");
389+
assertNotSame(match, match3);
390+
assertEquals("{\"b\":[]}", root2.toString());
391+
392+
// and then failing case
393+
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
394+
try {
395+
root3.withArrayProperty("c");
396+
fail("Should not pass");
397+
} catch (JsonNodeException e) {
398+
verifyException(e, "Cannot replace `JsonNode` of type ");
399+
}
400+
}
320401
}

0 commit comments

Comments
 (0)