Skip to content

Commit aaf270d

Browse files
authored
Implement #3568 (#3570)
1 parent 6abe555 commit aaf270d

File tree

9 files changed

+225
-80
lines changed

9 files changed

+225
-80
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ Project: jackson-databind
5555
custom `NullValueProvider`
5656
#3535: Replace `JsonNode.with()` with `JsonNode.withObject()`
5757
#3559: Support `null`-valued `Map` fields with "any setter"
58+
#3568: Change `JsonNode.with(String)` and `withArray(String)` to consider
59+
argument as `JsonPointer` if valid expression
5860

5961
2.13.4 (not yet released)
6062

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

+69-21
Original file line numberDiff line numberDiff line change
@@ -1119,22 +1119,33 @@ public final List<JsonNode> findParents(String fieldName)
11191119
*/
11201120

11211121
/**
1122-
* Method that can be called on Object nodes, to access a property
1123-
* that has Object value; or if no such property exists, to create,
1124-
* add and return such Object node.
1125-
* If the node method is called on is not Object node,
1126-
* or if property exists and has value that is not Object node,
1127-
* {@link UnsupportedOperationException} is thrown
1122+
* Short-cut equivalent to:
1123+
*<pre>
1124+
* withObject(JsonPointer.compile(expr);
1125+
*</pre>
1126+
* see {@link #withObject(JsonPointer)} for full explanation.
11281127
*
1129-
* @param propertyName Name of property for the {@link ObjectNode}
1128+
* @param expr {@link JsonPointer} expression to use
11301129
*
11311130
* @return {@link ObjectNode} found or created
11321131
*
11331132
* @since 2.14
11341133
*/
1135-
public ObjectNode withObject(String propertyName) {
1136-
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but "
1137-
+getClass().getName()+"), cannot call `withObject()` on it");
1134+
public final ObjectNode withObject(String expr) {
1135+
return withObject(JsonPointer.compile(expr));
1136+
}
1137+
1138+
/**
1139+
* Short-cut equivalent to:
1140+
*<pre>
1141+
* withObject(JsonPointer.compile(expr), overwriteMode, preferIndex);
1142+
*</pre>
1143+
*
1144+
* @since 2.14
1145+
*/
1146+
public final ObjectNode withObject(String expr,
1147+
OverwriteMode overwriteMode, boolean preferIndex) {
1148+
return withObject(JsonPointer.compile(expr), overwriteMode, preferIndex);
11381149
}
11391150

11401151
/**
@@ -1231,31 +1242,68 @@ public ObjectNode withObject(JsonPointer ptr,
12311242
}
12321243

12331244
/**
1245+
* Method that works in one of possible ways, depending on whether
1246+
* {@code exprOrProperty} is a valid {@link JsonPointer} expression or
1247+
* not (valid expression is either empty String {@code ""} or starts
1248+
* with leading slash {@code /} character).
1249+
* If it is, works as a short-cut to:
1250+
*<pre>
1251+
* withObject(JsonPointer.compile(exprOrProperty));
1252+
*</pre>
1253+
* If it is NOT a valid {@link JsonPointer} expression, value is taken
1254+
* as a literal Object property name and traversed like a single-segment
1255+
* {@link JsonPointer}.
1256+
*<p>
1257+
* NOTE: before Jackson 2.14 behavior was always that of non-expression usage;
1258+
* that is, {@code exprOrProperty} was always considered as a simple property name.
1259+
*
12341260
* @deprecated Since 2.14 use {@code withObject(String)} instead
12351261
*/
1236-
@SuppressWarnings("unchecked")
12371262
@Deprecated // since 2.14
1238-
public final <T extends JsonNode> T with(String propertyName) {
1239-
return (T) withObject(propertyName);
1263+
public <T extends JsonNode> T with(String exprOrProperty) {
1264+
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but "
1265+
+getClass().getName()+"), cannot call `with(String)` on it");
12401266
}
12411267

12421268
/**
1243-
* Method that can be called on {@link ObjectNode} nodes, to access a property
1244-
* that has <code>Array</code> value; or if no such property exists, to create,
1245-
* add and return such Array node.
1246-
* If the node method is called on is not Object node,
1247-
* or if property exists and has value that is not Array node,
1248-
* {@link UnsupportedOperationException} is thrown
1269+
* Method that works in one of possible ways, depending on whether
1270+
* {@code exprOrProperty} is a valid {@link JsonPointer} expression or
1271+
* not (valid expression is either empty String {@code ""} or starts
1272+
* with leading slash {@code /} character).
1273+
* If it is, works as a short-cut to:
1274+
*<pre>
1275+
* withObject(JsonPointer.compile(exprOrProperty));
1276+
*</pre>
1277+
* If it is NOT a valid {@link JsonPointer} expression, value is taken
1278+
* as a literal Object property name and traversed like a single-segment
1279+
* {@link JsonPointer}.
1280+
*<p>
1281+
* NOTE: before Jackson 2.14 behavior was always that of non-expression usage;
1282+
* that is, {@code exprOrProperty} was always considered as a simple property name.
12491283
*
1250-
* @param propertyName Name of property for the {@link ArrayNode}
1284+
* @param exprOrProperty Either {@link JsonPointer} expression for full access (if valid
1285+
* pointer expression), or the name of property for the {@link ArrayNode}.
12511286
*
12521287
* @return {@link ArrayNode} found or created
12531288
*/
1254-
public <T extends JsonNode> T withArray(String propertyName) {
1289+
public <T extends JsonNode> T withArray(String exprOrProperty) {
12551290
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but `"
12561291
+getClass().getName()+")`, cannot call `withArray()` on it");
12571292
}
12581293

1294+
/**
1295+
* Short-cut equivalent to:
1296+
*<pre>
1297+
* withArray(JsonPointer.compile(expr), overwriteMode, preferIndex);
1298+
*</pre>
1299+
*
1300+
* @since 2.14
1301+
*/
1302+
public ArrayNode withArray(String expr,
1303+
OverwriteMode overwriteMode, boolean preferIndex) {
1304+
return withArray(JsonPointer.compile(expr), overwriteMode, preferIndex);
1305+
}
1306+
12591307
/**
12601308
* Same as {@link #withArray(JsonPointer, OverwriteMode, boolean)} but
12611309
* with defaults of {@code OvewriteMode#NULLS} (overwrite mode)

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

+22
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ public ArrayNode deepCopy()
7171
/**********************************************************
7272
*/
7373

74+
@SuppressWarnings("unchecked")
75+
@Deprecated
76+
@Override
77+
public ObjectNode with(String exprOrProperty) {
78+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
79+
if (ptr != null) {
80+
return withObject(ptr);
81+
}
82+
return super.with(exprOrProperty); // to give failure
83+
}
84+
85+
@SuppressWarnings("unchecked")
86+
@Override
87+
public ArrayNode withArray(String exprOrProperty)
88+
{
89+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
90+
if (ptr != null) {
91+
return withArray(ptr);
92+
}
93+
return super.withArray(exprOrProperty); // to give failure
94+
}
95+
7496
@Override
7597
protected ObjectNode _withObject(JsonPointer origPtr,
7698
JsonPointer currentPtr,

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

+9
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,13 @@ protected <T> T _reportWrongNodeType(String msgTemplate, Object...args) {
260260
protected <T> T _reportWrongNodeOperation(String msgTemplate, Object...args) {
261261
throw new UnsupportedOperationException(String.format(msgTemplate, args));
262262
}
263+
264+
// @since 2.14
265+
protected JsonPointer _jsonPointerIfValid(String exprOrProperty) {
266+
if (exprOrProperty.isEmpty() || exprOrProperty.charAt(0) == '/') {
267+
return JsonPointer.compile(exprOrProperty);
268+
}
269+
return null;
270+
}
271+
263272
}

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

+44-34
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,50 @@ public ObjectNode deepCopy()
6767
/**********************************************************
6868
*/
6969

70+
@SuppressWarnings("unchecked")
71+
@Deprecated
72+
@Override
73+
public ObjectNode with(String exprOrProperty) {
74+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
75+
if (ptr != null) {
76+
return withObject(ptr);
77+
}
78+
JsonNode n = _children.get(exprOrProperty);
79+
if (n != null) {
80+
if (n instanceof ObjectNode) {
81+
return (ObjectNode) n;
82+
}
83+
throw new UnsupportedOperationException("Property '" + exprOrProperty
84+
+ "' has value that is not of type `ObjectNode` (but `" + n
85+
.getClass().getName() + "`)");
86+
}
87+
ObjectNode result = objectNode();
88+
_children.put(exprOrProperty, result);
89+
return result;
90+
}
91+
92+
@SuppressWarnings("unchecked")
93+
@Override
94+
public ArrayNode withArray(String exprOrProperty)
95+
{
96+
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
97+
if (ptr != null) {
98+
return withArray(ptr);
99+
}
100+
JsonNode n = _children.get(exprOrProperty);
101+
if (n != null) {
102+
if (n instanceof ArrayNode) {
103+
return (ArrayNode) n;
104+
}
105+
throw new UnsupportedOperationException("Property '" + exprOrProperty
106+
+ "' has value that is not of type `ArrayNode` (but `" + n
107+
.getClass().getName() + "`)");
108+
}
109+
ArrayNode result = arrayNode();
110+
_children.put(exprOrProperty, result);
111+
return result;
112+
}
113+
70114
@Override
71115
protected ObjectNode _withObject(JsonPointer origPtr,
72116
JsonPointer currentPtr,
@@ -239,40 +283,6 @@ public Iterator<Map.Entry<String, JsonNode>> fields() {
239283
return _children.entrySet().iterator();
240284
}
241285

242-
@Override
243-
public ObjectNode withObject(String propertyName) {
244-
JsonNode n = _children.get(propertyName);
245-
if (n != null) {
246-
if (n instanceof ObjectNode) {
247-
return (ObjectNode) n;
248-
}
249-
throw new UnsupportedOperationException("Property '" + propertyName
250-
+ "' has value that is not of type `ObjectNode` (but `" + n
251-
.getClass().getName() + "`)");
252-
}
253-
ObjectNode result = objectNode();
254-
_children.put(propertyName, result);
255-
return result;
256-
}
257-
258-
@SuppressWarnings("unchecked")
259-
@Override
260-
public ArrayNode withArray(String propertyName)
261-
{
262-
JsonNode n = _children.get(propertyName);
263-
if (n != null) {
264-
if (n instanceof ArrayNode) {
265-
return (ArrayNode) n;
266-
}
267-
throw new UnsupportedOperationException("Property '" + propertyName
268-
+ "' has value that is not of type `ArrayNode` (but `" + n
269-
.getClass().getName() + "`)");
270-
}
271-
ArrayNode result = arrayNode();
272-
_children.put(propertyName, result);
273-
return result;
274-
}
275-
276286
@Override
277287
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
278288
{

src/test/java/com/fasterxml/jackson/databind/node/JsonPointerWithNodeTest.java renamed to src/test/java/com/fasterxml/jackson/databind/node/JsonPointerAtNodeTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.fasterxml.jackson.core.JsonPointer;
44
import com.fasterxml.jackson.databind.*;
55

6-
public class JsonPointerWithNodeTest
6+
public class JsonPointerAtNodeTest
77
extends BaseMapTest
88
{
99
private final ObjectMapper MAPPER = newJsonMapper();

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void testObjectNodeSerialization() throws Exception
2525
root.put("answer", 42);
2626
ArrayNode arr = root.withArray("matrix");
2727
arr.add(1).add(12345678901L).add(true).add("...");
28-
ObjectNode misc = root.withObject("misc");
28+
ObjectNode misc = root.withObject("/misc");
2929
misc.put("value", 0.25);
3030

3131
testNodeRoundtrip(root);

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ public void testValidWith() throws Exception
303303
{
304304
ObjectNode root = MAPPER.createObjectNode();
305305
assertEquals("{}", MAPPER.writeValueAsString(root));
306-
JsonNode child = root.withObject("prop");
306+
JsonNode child = root.withObject("/prop");
307307
assertTrue(child instanceof ObjectNode);
308308
assertEquals("{\"prop\":{}}", MAPPER.writeValueAsString(root));
309309
}
@@ -321,7 +321,7 @@ public void testInvalidWith() throws Exception
321321
{
322322
JsonNode root = MAPPER.createArrayNode();
323323
try { // should not work for non-ObjectNode nodes:
324-
root.withObject("prop");
324+
root.with("prop");
325325
fail("Expected exception");
326326
} catch (UnsupportedOperationException e) {
327327
verifyException(e, "not of type `ObjectNode`");
@@ -330,7 +330,7 @@ public void testInvalidWith() throws Exception
330330
ObjectNode root2 = MAPPER.createObjectNode();
331331
root2.put("prop", 13);
332332
try { // should not work for non-ObjectNode nodes:
333-
root2.withObject("prop");
333+
root2.with("prop");
334334
fail("Expected exception");
335335
} catch (UnsupportedOperationException e) {
336336
verifyException(e, "has value that is not");

0 commit comments

Comments
 (0)