Skip to content

Commit

Permalink
Merge branch '2.16'
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Sep 30, 2023
2 parents f5036d6 + b9ee863 commit ea47946
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 19 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Project: jackson-databind
#4090: Support sequenced collections (JDK 21)S
(contributed by @pjfanning)
#4095: Add `withObjectProperty(String)`, `withArrayProperty(String)` in `JsonNode`
#4096: Change `JsonNode.withObject(String)` to work similar to `withArray()`
wrt argument
#4122: Do not resolve wildcards if upper bound is too non-specific
(contributed by @yawkat)

Expand Down
40 changes: 26 additions & 14 deletions src/main/java/tools/jackson/databind/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -1105,18 +1105,29 @@ public final List<JsonNode> findParents(String fieldName)
*/

/**
* Short-cut equivalent to:
* Method that works in one of possible ways, depending on whether
* {@code exprOrProperty} is a valid {@link JsonPointer} expression or
* not (valid expression is either empty String {@code ""} or starts
* with leading slash {@code /} character).
* If it is, works as a short-cut to:
*<pre>
* withObject(JsonPointer.compile(exprOrProperty));
*</pre>
* If it is NOT a valid {@link JsonPointer} expression, value is taken
* as a literal Object property name and calls is alias for
*<pre>
* withObject(JsonPointer.compile(expr);
* withObjectProperty(exprOrProperty);
*</pre>
* see {@link #withObject(JsonPointer)} for full explanation.
*
* @param expr {@link JsonPointer} expression to use
* @param exprOrProperty {@link JsonPointer} expression to use (if valid as one),
* or, if not (no leading "/"), property name to match.
*
* @return {@link ObjectNode} found or created
*/
public final ObjectNode withObject(String expr) {
return withObject(JsonPointer.compile(expr));
public ObjectNode withObject(String exprOrProperty) {
// To avoid abstract method, base implementation just fails
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
+getClass().getName()+")`, cannot call `withObject()` on it");
}

/**
Expand Down Expand Up @@ -1215,9 +1226,8 @@ public final ObjectNode withObject(JsonPointer ptr) {
public ObjectNode withObject(JsonPointer ptr,
OverwriteMode overwriteMode, boolean preferIndex) {
// To avoid abstract method, base implementation just fails
return _reportUnsupportedOperation(
"`withObject(JsonPointer)` not implemented by `%s`",
getClass().getName());
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
+getClass().getName()+")`, cannot call `withObject()` on it");
}

/**
Expand Down Expand Up @@ -1246,17 +1256,19 @@ public ObjectNode withObject(JsonPointer ptr,
*/
public ObjectNode withObjectProperty(String propName) {
// To avoid abstract method, base implementation just fails
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but `"
+getClass().getName()+")`, cannot call `withObjectProperty(String)` on it");
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
+getClass().getName()+")`, cannot call `withObjectProperty()` on it");
}

/** Short-cut equivalent to:
*<pre>
* withArray(JsonPointer.compile(expr), overwriteMode, preferIndex);
*</pre>
*/
public final ArrayNode withArray(String exprOrProperty) {
return withArray(JsonPointer.compile(exprOrProperty));
public ArrayNode withArray(String exprOrProperty) {
// To avoid abstract method, base implementation just fails
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
+getClass().getName()+")`, cannot call `withArray()` on it");
}

/**
Expand Down Expand Up @@ -1375,7 +1387,7 @@ public ArrayNode withArray(JsonPointer ptr,
*/
public ArrayNode withArrayProperty(String propName) {
// To avoid abstract method, base implementation just fails
throw new UnsupportedOperationException("`JsonNode` not of type `ObjectNode` (but `"
return _reportUnsupportedOperation("`JsonNode` not of type `ObjectNode` (but `"
+getClass().getName()+")`, cannot call `withArrayProperty(String)` on it");
}

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/tools/jackson/databind/node/BaseJsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,11 @@ protected BigInteger _bigIntFromBigDec(BigDecimal value) {
StreamReadConstraints.defaults().validateBigIntegerScale(value.scale());
return value.toBigInteger();
}

protected JsonPointer _jsonPointerIfValid(String exprOrProperty) {
if (exprOrProperty.isEmpty() || exprOrProperty.charAt(0) == '/') {
return JsonPointer.compile(exprOrProperty);
}
return null;
}
}
18 changes: 18 additions & 0 deletions src/main/java/tools/jackson/databind/node/ObjectNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ public ObjectNode deepCopy()
/**********************************************************************
*/

@Override
public ObjectNode withObject(String exprOrProperty) {
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
if (ptr != null) {
return withObject(ptr);
}
return withObjectProperty(exprOrProperty);
}

@Override
public ObjectNode withObjectProperty(String propName) {
JsonNode child = _children.get(propName);
Expand All @@ -77,6 +86,15 @@ public ObjectNode withObjectProperty(String propName) {
child.getClass().getName(), propName, OverwriteMode.NULLS);
}

@Override
public ArrayNode withArray(String exprOrProperty) {
JsonPointer ptr = _jsonPointerIfValid(exprOrProperty);
if (ptr != null) {
return withArray(ptr);
}
return withArrayProperty(exprOrProperty);
}

@Override
public ArrayNode withArrayProperty(String propName) {
JsonNode child = _children.get(propName);
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/tools/jackson/databind/node/ObjectNodeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ public void testInvalidWithObject() throws Exception
root.withObject("/prop");
fail("Expected exception");
} catch (JsonNodeException e) {
verifyException(e, "Cannot replace context node (of type");
verifyException(e, "`JsonNode` not of type `ObjectNode`");
verifyException(e, "ArrayNode");
}
// also: should fail of we already have non-object property
Expand All @@ -342,7 +342,7 @@ public void testInvalidWithArray() throws Exception
root.withArray("/prop");
fail("Expected exception");
} catch (JsonNodeException e) {
verifyException(e, "Cannot replace context node (of type");
verifyException(e, "`JsonNode` not of type `ObjectNode`");
verifyException(e, "ArrayNode");
}
// also: should fail of we already have non-Array property
Expand Down
83 changes: 80 additions & 3 deletions src/test/java/tools/jackson/databind/node/WithPathTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,11 @@ private void _verifyObjectReplaceFail(JsonNode doc, JsonPointer ptr, OverwriteMo

/*
/**********************************************************************
/* Test methods, withObjectProperty()
/* Test methods, withObjectProperty()/withObject(exprOrProperty)
/**********************************************************************
*/

// [databind#4095]
public void testWithObjectProperty() throws Exception
{
ObjectNode root = MAPPER.createObjectNode();
Expand All @@ -228,7 +229,7 @@ public void testWithObjectProperty() throws Exception
ObjectNode match3 = root2.withObjectProperty("b");
assertNotSame(match, match3);
assertEquals("{\"b\":{}}", root2.toString());

// and then failing case
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
try {
Expand All @@ -239,6 +240,46 @@ public void testWithObjectProperty() throws Exception
}
}

// [databind#4096]
public void testWithObjectAdnExprOrProp() throws Exception
{
ObjectNode root = MAPPER.createObjectNode();

// First: create new property value
ObjectNode match = root.withObject("a");
assertTrue(match.isObject());
assertEquals(a2q("{}"), match.toString());
match.put("value", 42);
assertEquals(a2q("{'a':{'value':42}}"), root.toString());

// and then with JsonPointer expr
match = root.withObject("/a/b");
assertTrue(match.isObject());
assertEquals(a2q("{}"), match.toString());
assertEquals(a2q("{'a':{'value':42,'b':{}}}"), root.toString());

// Then existing prop:
assertEquals(a2q("{'value':42,'b':{}}"),
root.withObject("a").toString());
assertEquals(a2q("{}"),
root.withObject("/a/b").toString());

// and then failing case
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
try {
root3.withObject("c");
fail("Should not pass");
} catch (JsonNodeException e) {
verifyException(e, "Cannot replace `JsonNode` of type ");
}
try {
root3.withObject("/c");
fail("Should not pass");
} catch (JsonNodeException e) {
verifyException(e, "Cannot replace `JsonNode` of type ");
}
}

/*
/**********************************************************************
/* Test methods, withArray()
Expand Down Expand Up @@ -361,10 +402,11 @@ public void testWithArray3882() throws Exception

/*
/**********************************************************************
/* Test methods, withArrayProperty()
/* Test methods, withArrayProperty()/withArray(exprOrProperty)
/**********************************************************************
*/

// [databind#4095]
public void testWithArrayProperty() throws Exception
{
ObjectNode root = MAPPER.createObjectNode();
Expand Down Expand Up @@ -398,4 +440,39 @@ public void testWithArrayProperty() throws Exception
verifyException(e, "Cannot replace `JsonNode` of type ");
}
}

// [databind#4096]
public void testWithArrayAndExprOrProp() throws Exception
{
ObjectNode root = MAPPER.createObjectNode();

// First: create new property value
ArrayNode match = root.withArray("a");
assertTrue(match.isArray());
assertEquals(a2q("[]"), match.toString());
match.add(42);
assertEquals(a2q("{'a':[42]}"), root.toString());

match = root.withArray("/b");
assertEquals(a2q("{'a':[42],'b':[]}"), root.toString());

// Second: match existing Object property
assertEquals(a2q("[42]"), root.withArray("a").toString());
assertEquals(a2q("[42]"), root.withArray("/a").toString());

// and then failing case
JsonNode root3 = MAPPER.readTree("{\"c\": 123}");
try {
root3.withArray("c");
fail("Should not pass");
} catch (JsonNodeException e) {
verifyException(e, "Cannot replace `JsonNode` of type ");
}
try {
root3.withArray("/c");
fail("Should not pass: resulting doc = "+root3);
} catch (JsonNodeException e) {
verifyException(e, "Cannot replace `JsonNode` of type ");
}
}
}

0 comments on commit ea47946

Please sign in to comment.