Skip to content

Commit

Permalink
Continue with #1980
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jul 6, 2022
1 parent 0bd8779 commit 56fb5d3
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 20 deletions.
67 changes: 66 additions & 1 deletion src/main/java/com/fasterxml/jackson/databind/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.ClassUtil;

/**
Expand Down Expand Up @@ -40,6 +41,45 @@ public abstract class JsonNode
extends JsonSerializable.Base // i.e. implements JsonSerializable
implements TreeNode, Iterable<JsonNode>
{
/**
* Configuration setting used with {@link JsonNode#withObject(JsonPointer)}
* method overrides, to indicate which overwrites are acceptable if the
* path pointer indicates has incompatible nodes (for example, instead
* of Object node a Null node is encountered).
* Overwrite means that the existing value is replaced with compatible type,
* potentially losing existing values or even sub-trees.
*<p>
* Default value if {@code NULLS} which only allows Null-value nodes
* to be replaced but no other types.
*
* @since 2.14
*/
public enum OverwriteMode {
/**
* Mode in which no values may be overwritten, not even {@code NullNode}s;
* only compatible paths may be traversed.
*/
NONE,

/**
* Mode in which explicit {@code NullNode}s may be replaced but no other
* node types.
*/
NULLS,

/**
* Mode in which all scalar value nodes may be replaced, but not
* Array or Object nodes.
*/
SCALARS,

/**
* Mode in which all incompatible node types may be replaced, including
* Array and Object nodes where necessary.
*/
ALL;
}

/*
/**********************************************************
/* Construction, related
Expand Down Expand Up @@ -1090,6 +1130,24 @@ public <T extends JsonNode> T withObject(String propertyName) {
+getClass().getName()+"), cannot call withObject() on it");
}

/**
* Same as {@link #withObject(JsonPointer, OverwriteMode, boolean)} but
* with defaults of {@code OvewriteMode#NULLS} (overwrite mode)
* and {@code true} for {@code preferIndex} (that is, will try to
* consider {@link JsonPointer} segments index if at all possible
* and only secondarily as property name
*
* @param ptr Pointer that indicates path to use for Object value to return
* (potentially creating as necessary)
*
* @return ObjectNode found or created
*
* @since 2.14
*/
public final ObjectNode withObject(JsonPointer ptr) {
return withObject(ptr, OverwriteMode.NULLS, true);
}

/**
* Method that can be called on Object nodes, to access a Object-valued
* node pointed to by given {@link JsonPointer}, if such a node exists:
Expand All @@ -1098,9 +1156,16 @@ public <T extends JsonNode> T withObject(String propertyName) {
* or if property exists and has value that is not Object node,
* {@link UnsupportedOperationException} is thrown
*
* @param ptr Pointer that indicates path to use for Object value to return
* (potentially creating as necessary)
* @param overwriteMode Defines w
*
* @return ObjectNode found or created
*
* @since 2.14
*/
public <T extends JsonNode> T withObject(JsonPointer ptr) {
public ObjectNode withObject(JsonPointer ptr,
OverwriteMode overwriteMode, boolean preferIndex) {
// To avoid abstract method, base implementation just fails
throw new UnsupportedOperationException("`withObject(JsonPointer)` not implemented by "
+getClass().getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ public ArrayNode deepCopy()
return ret;
}

@SuppressWarnings("unchecked")
@Override
protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
JsonPointer currentPtr)
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
JsonPointer currentPtr,
OverwriteMode overwriteMode, boolean preferIndex)
{
// With Arrays, bit different; the first entry needs to be index
if (!currentPtr.mayMatchElement()) {
Expand Down Expand Up @@ -99,7 +99,7 @@ protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
currentNode = currentNode.putObject(currentPtr.getMatchingProperty());
currentPtr = currentPtr.tail();
}
return (T) currentNode;
return (ObjectNode) currentNode;
}

/*
Expand Down
26 changes: 15 additions & 11 deletions src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,42 +110,46 @@ public JsonParser.NumberType numberType() {
*/

@Override
public <T extends JsonNode> T withObject(JsonPointer ptr) {
public ObjectNode withObject(JsonPointer ptr,
OverwriteMode overwriteMode, boolean preferIndex) {
if (!isObject()) {
// To avoid abstract method, base implementation just fails
_reportWrongNodeType("Can only call `withObject()` on `ObjectNode`, not `%s`",
getClass().getName());
}
return _withObject(ptr, ptr);
return _withObject(ptr, ptr, overwriteMode, preferIndex);
}

@SuppressWarnings("unchecked")
protected <T extends JsonNode> T _withObject(JsonPointer origPtr,
JsonPointer currentPtr)
protected ObjectNode _withObject(JsonPointer origPtr,
JsonPointer currentPtr,
OverwriteMode overwriteMode, boolean preferIndex)
{
if (currentPtr.matches()) {
if (this.isObject()) {
return (T) this;
if (this instanceof ObjectNode) {
return (ObjectNode) this;
}
return _reportWrongNodeType(
"`JsonNode` matching `JsonPointer` \"%s\" must be `ObjectNode`, not `%s`",
origPtr.toString(),
getClass().getName());
}
JsonNode n = _at(currentPtr);
// If there's a path, follow it
if ((n != null) && (n instanceof BaseJsonNode)) {
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail());
return ((BaseJsonNode) n)._withObject(origPtr, currentPtr.tail(),
overwriteMode, preferIndex);
}
return _withObjectCreatePath(origPtr, currentPtr);
return _withObjectCreatePath(origPtr, currentPtr, overwriteMode, preferIndex);
}

/**
* Helper method for constructing specified path under this node, if possible;
* or throwing an exception if not. If construction successful, needs to return
* the innermost {@code ObjectNode} constructed.
*/
protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
JsonPointer currentPtr)
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
JsonPointer currentPtr,
OverwriteMode overwriteMode, boolean preferIndex)
{
// Cannot traverse non-container nodes:
return _reportWrongNodeType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@ public ObjectNode deepCopy()
return ret;
}

@SuppressWarnings("unchecked")
@Override
protected <T extends JsonNode> T _withObjectCreatePath(JsonPointer origPtr,
JsonPointer currentPtr)
protected ObjectNode _withObjectCreatePath(JsonPointer origPtr,
JsonPointer currentPtr,
OverwriteMode overwriteMode, boolean preferIndex)
{
ObjectNode currentNode = this;
while (!currentPtr.matches()) {
// Should we try to build Arrays? For now, nope.
currentNode = currentNode.putObject(currentPtr.getMatchingProperty());
currentPtr = currentPtr.tail();
}
return (T) currentNode;
return currentNode;
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,38 @@ public void testValidWithObjectSimple() throws Exception
root.toString());
}

public void testObjectPathWithReplace() throws Exception
{
final JsonPointer abPath = JsonPointer.compile("/a/b");
ObjectNode root = MAPPER.createObjectNode();
root.put("a", 13);

// First, without replacement (default) get exception
try {
root.withObject(abPath);
fail("Should not pass");
} catch (UnsupportedOperationException e) {
verifyException(e, "cannot traverse non-container");
}

// Except fine via nulls (by default)
/*
root.putNull("a");
root.withObject(abPath).put("value", 42);
assertEquals(a2q("{'a':{'b':{'value':42}}}"),
root.toString());
// but not if prevented
root = (ObjectNode) MAPPER.readTree(a2q("{'a':null}"));
try {
root.withObject(abPath);
fail("Should not pass");
} catch (UnsupportedOperationException e) {
verifyException(e, "cannot traverse non-container");
}
*/
}

public void testValidWithObjectWithArray() throws Exception
{
ObjectNode root = MAPPER.createObjectNode();
Expand All @@ -56,5 +88,7 @@ public void testValidWithObjectWithArray() throws Exception
match.put("value2", true);
assertEquals(a2q("{'arr':[null,null,{'value':42,'value2':true}]}"),
root.toString());

// And even more! `null`s can be replaced
}
}

0 comments on commit 56fb5d3

Please sign in to comment.