Skip to content

Commit 2b3389d

Browse files
committed
Merge branch '2.14'
2 parents 735b5b5 + 56fb5d3 commit 2b3389d

File tree

5 files changed

+121
-14
lines changed

5 files changed

+121
-14
lines changed

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

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,45 @@ public abstract class JsonNode
4141
extends JacksonSerializable.Base // i.e. implements JacksonSerializable
4242
implements TreeNode, Iterable<JsonNode>
4343
{
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+
4483
/*
4584
/**********************************************************************
4685
/* Construction, related
@@ -1058,15 +1097,42 @@ public final List<JsonNode> findParents(String fieldName)
10581097
*/
10591098
public abstract ObjectNode withObject(String propertyName);
10601099

1100+
/**
1101+
* Same as {@link #withObject(JsonPointer, OverwriteMode, boolean)} but
1102+
* with defaults of {@code OvewriteMode#NULLS} (overwrite mode)
1103+
* and {@code true} for {@code preferIndex} (that is, will try to
1104+
* consider {@link JsonPointer} segments index if at all possible
1105+
* and only secondarily as property name
1106+
*
1107+
* @param ptr Pointer that indicates path to use for Object value to return
1108+
* (potentially creating as necessary)
1109+
*
1110+
* @return ObjectNode found or created
1111+
*
1112+
* @since 2.14
1113+
*/
1114+
public final ObjectNode withObject(JsonPointer ptr) {
1115+
return withObject(ptr, OverwriteMode.NULLS, true);
1116+
}
1117+
10611118
/**
10621119
* Method that can be called on Object nodes, to access a Object-valued
10631120
* node pointed to by given {@link JsonPointer}, if such a node exists:
10641121
* if not, an attempt is made to create it.
10651122
* If the node method is called on is not Object node,
10661123
* or if property exists and has value that is not Object node,
10671124
* {@link DatabindException} is thrown
1068-
*/
1069-
public abstract ObjectNode withObject(JsonPointer ptr);
1125+
*
1126+
* @param ptr Pointer that indicates path to use for Object value to return
1127+
* (potentially creating as necessary)
1128+
* @param overwriteMode Defines w
1129+
*/
1130+
public ObjectNode withObject(JsonPointer ptr,
1131+
OverwriteMode overwriteMode, boolean preferIndex) {
1132+
// To avoid abstract method, base implementation just fails
1133+
throw new UnsupportedOperationException("`withObject(JsonPointer)` not implemented by "
1134+
+getClass().getName());
1135+
}
10701136

10711137
/**
10721138
* Method that can be called on Object nodes, to access a property

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public ArrayNode deepCopy()
6363

6464
@Override
6565
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
66-
JsonPointer currentPtr)
66+
JsonPointer currentPtr,
67+
OverwriteMode overwriteMode, boolean preferIndex)
6768
{
6869
// With Arrays, bit different; the first entry needs to be index
6970
if (!currentPtr.mayMatchElement()) {
@@ -91,7 +92,7 @@ protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
9192
currentNode = currentNode.putObject(currentPtr.getMatchingProperty());
9293
currentPtr = currentPtr.tail();
9394
}
94-
return currentNode;
95+
return (ObjectNode) currentNode;
9596
}
9697

9798
/*

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public JsonParser.NumberType numberType() {
114114

115115
/*
116116
/**********************************************************************
117-
/* JacksonSerializable
117+
/* With-Traversal
118118
/**********************************************************************
119119
*/
120120

@@ -123,23 +123,25 @@ public ObjectNode withObject(String propertyName) {
123123
return _reportWrongNodeType(
124124
"Can only call `withObject(String)` on `ObjectNode`, not `%s`",
125125
getClass().getName());
126-
}
126+
}
127127

128128
@Override
129-
public ObjectNode withObject(JsonPointer ptr) {
129+
public ObjectNode withObject(JsonPointer ptr,
130+
OverwriteMode overwriteMode, boolean preferIndex) {
130131
if (!isObject()) {
131132
// To avoid abstract method, base implementation just fails
132133
_reportWrongNodeType("Can only call `withObject(JsonPointer)` on `ObjectNode`, not `%s`",
133134
getClass().getName());
134135
}
135-
return _withObject(ptr, ptr);
136+
return _withObject(ptr, ptr, overwriteMode, preferIndex);
136137
}
137138

138139
protected ObjectNode _withObject(JsonPointer origPtr,
139-
JsonPointer currentPtr)
140+
JsonPointer currentPtr,
141+
OverwriteMode overwriteMode, boolean preferIndex)
140142
{
141143
if (currentPtr.matches()) {
142-
if (this.isObject()) {
144+
if (this instanceof ObjectNode) {
143145
return (ObjectNode) this;
144146
}
145147
return _reportWrongNodeType(
@@ -148,10 +150,12 @@ protected ObjectNode _withObject(JsonPointer origPtr,
148150
getClass().getName());
149151
}
150152
JsonNode n = _at(currentPtr);
153+
// If there's a path, follow it
151154
if ((n != null) && (n instanceof BaseJsonNode)) {
152-
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail());
155+
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail(),
156+
overwriteMode, preferIndex);
153157
}
154-
return _withObjectCreatePath(origPtr, currentPtr);
158+
return _withObjectCreatePath(origPtr, currentPtr, overwriteMode, preferIndex);
155159
}
156160

157161
/**
@@ -160,7 +164,8 @@ protected ObjectNode _withObject(JsonPointer origPtr,
160164
* the innermost {@code ObjectNode} constructed.
161165
*/
162166
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
163-
JsonPointer currentPtr)
167+
JsonPointer currentPtr,
168+
OverwriteMode overwriteMode, boolean preferIndex)
164169
{
165170
// Cannot traverse non-container nodes:
166171
return _reportWrongNodeType(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public ObjectNode deepCopy()
5858

5959
@Override
6060
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
61-
JsonPointer currentPtr)
61+
JsonPointer currentPtr,
62+
OverwriteMode overwriteMode, boolean preferIndex)
6263
{
6364
ObjectNode currentNode = this;
6465
while (!currentPtr.matches()) {

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

Lines changed: 34 additions & 0 deletions
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)