Skip to content

Commit 56fb5d3

Browse files
committed
Continue with #1980
1 parent 0bd8779 commit 56fb5d3

File tree

5 files changed

+123
-20
lines changed

5 files changed

+123
-20
lines changed

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

+66-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.core.*;
99
import com.fasterxml.jackson.databind.node.JsonNodeType;
1010
import com.fasterxml.jackson.databind.node.MissingNode;
11+
import com.fasterxml.jackson.databind.node.ObjectNode;
1112
import com.fasterxml.jackson.databind.util.ClassUtil;
1213

1314
/**
@@ -40,6 +41,45 @@ public abstract class JsonNode
4041
extends JsonSerializable.Base // i.e. implements JsonSerializable
4142
implements TreeNode, Iterable<JsonNode>
4243
{
44+
/**
45+
* Configuration setting used with {@link JsonNode#withObject(JsonPointer)}
46+
* method overrides, to indicate which overwrites are acceptable if the
47+
* path pointer indicates has incompatible nodes (for example, instead
48+
* of Object node a Null node is encountered).
49+
* Overwrite means that the existing value is replaced with compatible type,
50+
* potentially losing existing values or even sub-trees.
51+
*<p>
52+
* Default value if {@code NULLS} which only allows Null-value nodes
53+
* to be replaced but no other types.
54+
*
55+
* @since 2.14
56+
*/
57+
public enum OverwriteMode {
58+
/**
59+
* Mode in which no values may be overwritten, not even {@code NullNode}s;
60+
* only compatible paths may be traversed.
61+
*/
62+
NONE,
63+
64+
/**
65+
* Mode in which explicit {@code NullNode}s may be replaced but no other
66+
* node types.
67+
*/
68+
NULLS,
69+
70+
/**
71+
* Mode in which all scalar value nodes may be replaced, but not
72+
* Array or Object nodes.
73+
*/
74+
SCALARS,
75+
76+
/**
77+
* Mode in which all incompatible node types may be replaced, including
78+
* Array and Object nodes where necessary.
79+
*/
80+
ALL;
81+
}
82+
4383
/*
4484
/**********************************************************
4585
/* Construction, related
@@ -1090,6 +1130,24 @@ public <T extends JsonNode> T withObject(String propertyName) {
10901130
+getClass().getName()+"), cannot call withObject() on it");
10911131
}
10921132

1133+
/**
1134+
* Same as {@link #withObject(JsonPointer, OverwriteMode, boolean)} but
1135+
* with defaults of {@code OvewriteMode#NULLS} (overwrite mode)
1136+
* and {@code true} for {@code preferIndex} (that is, will try to
1137+
* consider {@link JsonPointer} segments index if at all possible
1138+
* and only secondarily as property name
1139+
*
1140+
* @param ptr Pointer that indicates path to use for Object value to return
1141+
* (potentially creating as necessary)
1142+
*
1143+
* @return ObjectNode found or created
1144+
*
1145+
* @since 2.14
1146+
*/
1147+
public final ObjectNode withObject(JsonPointer ptr) {
1148+
return withObject(ptr, OverwriteMode.NULLS, true);
1149+
}
1150+
10931151
/**
10941152
* Method that can be called on Object nodes, to access a Object-valued
10951153
* node pointed to by given {@link JsonPointer}, if such a node exists:
@@ -1098,9 +1156,16 @@ public <T extends JsonNode> T withObject(String propertyName) {
10981156
* or if property exists and has value that is not Object node,
10991157
* {@link UnsupportedOperationException} is thrown
11001158
*
1159+
* @param ptr Pointer that indicates path to use for Object value to return
1160+
* (potentially creating as necessary)
1161+
* @param overwriteMode Defines w
1162+
*
1163+
* @return ObjectNode found or created
1164+
*
11011165
* @since 2.14
11021166
*/
1103-
public <T extends JsonNode> T withObject(JsonPointer ptr) {
1167+
public ObjectNode withObject(JsonPointer ptr,
1168+
OverwriteMode overwriteMode, boolean preferIndex) {
11041169
// To avoid abstract method, base implementation just fails
11051170
throw new UnsupportedOperationException("`withObject(JsonPointer)` not implemented by "
11061171
+getClass().getName());

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ public ArrayNode deepCopy()
6868
return ret;
6969
}
7070

71-
@SuppressWarnings("unchecked")
7271
@Override
73-
protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
74-
JsonPointer currentPtr)
72+
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
73+
JsonPointer currentPtr,
74+
OverwriteMode overwriteMode, boolean preferIndex)
7575
{
7676
// With Arrays, bit different; the first entry needs to be index
7777
if (!currentPtr.mayMatchElement()) {
@@ -99,7 +99,7 @@ protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
9999
currentNode = currentNode.putObject(currentPtr.getMatchingProperty());
100100
currentPtr = currentPtr.tail();
101101
}
102-
return (T) currentNode;
102+
return (ObjectNode) currentNode;
103103
}
104104

105105
/*

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

+15-11
Original file line numberDiff line numberDiff line change
@@ -110,42 +110,46 @@ public JsonParser.NumberType numberType() {
110110
*/
111111

112112
@Override
113-
public <T extends JsonNode> T withObject(JsonPointer ptr) {
113+
public ObjectNode withObject(JsonPointer ptr,
114+
OverwriteMode overwriteMode, boolean preferIndex) {
114115
if (!isObject()) {
115116
// To avoid abstract method, base implementation just fails
116117
_reportWrongNodeType("Can only call `withObject()` on `ObjectNode`, not `%s`",
117118
getClass().getName());
118119
}
119-
return _withObject(ptr, ptr);
120+
return _withObject(ptr, ptr, overwriteMode, preferIndex);
120121
}
121122

122-
@SuppressWarnings("unchecked")
123-
protected <T extends JsonNode> T _withObject(JsonPointer origPtr,
124-
JsonPointer currentPtr)
123+
protected ObjectNode _withObject(JsonPointer origPtr,
124+
JsonPointer currentPtr,
125+
OverwriteMode overwriteMode, boolean preferIndex)
125126
{
126127
if (currentPtr.matches()) {
127-
if (this.isObject()) {
128-
return (T) this;
128+
if (this instanceof ObjectNode) {
129+
return (ObjectNode) this;
129130
}
130131
return _reportWrongNodeType(
131132
"`JsonNode` matching `JsonPointer` \"%s\" must be `ObjectNode`, not `%s`",
132133
origPtr.toString(),
133134
getClass().getName());
134135
}
135136
JsonNode n = _at(currentPtr);
137+
// If there's a path, follow it
136138
if ((n != null) && (n instanceof BaseJsonNode)) {
137-
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail());
139+
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail(),
140+
overwriteMode, preferIndex);
138141
}
139-
return _withObjectCreatePath(origPtr, currentPtr);
142+
return _withObjectCreatePath(origPtr, currentPtr, overwriteMode, preferIndex);
140143
}
141144

142145
/**
143146
* Helper method for constructing specified path under this node, if possible;
144147
* or throwing an exception if not. If construction successful, needs to return
145148
* the innermost {@code ObjectNode} constructed.
146149
*/
147-
protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
148-
JsonPointer currentPtr)
150+
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
151+
JsonPointer currentPtr,
152+
OverwriteMode overwriteMode, boolean preferIndex)
149153
{
150154
// Cannot traverse non-container nodes:
151155
return _reportWrongNodeType(

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,18 @@ public ObjectNode deepCopy()
6060
return ret;
6161
}
6262

63-
@SuppressWarnings("unchecked")
6463
@Override
65-
protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
66-
JsonPointer currentPtr)
64+
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
65+
JsonPointer currentPtr,
66+
OverwriteMode overwriteMode, boolean preferIndex)
6767
{
6868
ObjectNode currentNode = this;
6969
while (!currentPtr.matches()) {
7070
// Should we try to build Arrays? For now, nope.
7171
currentNode = currentNode.putObject(currentPtr.getMatchingProperty());
7272
currentPtr = currentPtr.tail();
7373
}
74-
return (T) currentNode;
74+
return currentNode;
7575
}
7676

7777
/*

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

+34
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,38 @@ public void testValidWithObjectSimple() throws Exception
4040
root.toString());
4141
}
4242

43+
public void testObjectPathWithReplace() throws Exception
44+
{
45+
final JsonPointer abPath = JsonPointer.compile("/a/b");
46+
ObjectNode root = MAPPER.createObjectNode();
47+
root.put("a", 13);
48+
49+
// First, without replacement (default) get exception
50+
try {
51+
root.withObject(abPath);
52+
fail("Should not pass");
53+
} catch (UnsupportedOperationException e) {
54+
verifyException(e, "cannot traverse non-container");
55+
}
56+
57+
// Except fine via nulls (by default)
58+
/*
59+
root.putNull("a");
60+
root.withObject(abPath).put("value", 42);
61+
assertEquals(a2q("{'a':{'b':{'value':42}}}"),
62+
root.toString());
63+
64+
// but not if prevented
65+
root = (ObjectNode) MAPPER.readTree(a2q("{'a':null}"));
66+
try {
67+
root.withObject(abPath);
68+
fail("Should not pass");
69+
} catch (UnsupportedOperationException e) {
70+
verifyException(e, "cannot traverse non-container");
71+
}
72+
*/
73+
}
74+
4375
public void testValidWithObjectWithArray() throws Exception
4476
{
4577
ObjectNode root = MAPPER.createObjectNode();
@@ -56,5 +88,7 @@ public void testValidWithObjectWithArray() throws Exception
5688
match.put("value2", true);
5789
assertEquals(a2q("{'arr':[null,null,{'value':42,'value2':true}]}"),
5890
root.toString());
91+
92+
// And even more! `null`s can be replaced
5993
}
6094
}

0 commit comments

Comments
 (0)