Skip to content

Commit

Permalink
Add JsonNode.optional(String name) and optional(int index) methods (
Browse files Browse the repository at this point in the history
  • Loading branch information
JooHyukKim authored Dec 30, 2024
1 parent 3e0d39b commit 9e63036
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 2 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Project: jackson-databind

#1467: Support `@JsonUnwrapped` with `@JsonCreator`
(implementation by Liam F)
#2145: Add `JsonNode.optional(String name)` and `optional(int index)` methods
(fix by Joo-Hyuk K)
#2461: Nested `@JsonUnwrapped` property names not correctly handled
(reported by @plovell)
(fix contributed by @SandeepGaur2016)
Expand Down
47 changes: 47 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,53 @@ public boolean isObject() {
*/
@Override
public JsonNode get(String fieldName) { return null; }

/**
* Method for accessing value of the specified element of
* an array node, wrapped in an {@link Optional}. For other nodes,
* an empty Optional is always returned.
*<p>
* For array nodes, index specifies
* exact location within array and allows for efficient iteration
* over child elements (underlying storage is guaranteed to
* be efficiently indexable, i.e. has random-access to elements).
* If index is less than 0, or equal-or-greater than
* <code>node.size()</code>, an empty Optional is returned; no exception is
* thrown for any index.
*<p>
* NOTE: if the element value has been explicitly set as <code>null</code>
* (which is different from removal!),
* a {@link com.fasterxml.jackson.databind.node.NullNode} will be returned
* wrapped in an Optional, not an empty Optional.
*
* @return Optional containing the node that represents the value of the specified element,
* if this node is an array and has the specified element and otherwise, an
* empty Optional, never null.
*
* @since 2.19
*/
public Optional<JsonNode> optional(int index) { return Optional.empty(); }

/**
* Method for accessing value of the specified field of
* an object node. If this node is not an object (or it
* does not have a value for specified field name), or
* if there is no field with such name, empty {@link Optional}
* is returned.
*<p>
* NOTE: if the property value has been explicitly set as <code>null</code>
* (which is different from removal!), an Optional containing
* {@link com.fasterxml.jackson.databind.node.NullNode} will be returned,
* not null.
*
* @return Optional that may contain value of the specified field,
* if this node is an object and has value for the specified
* field. Empty Optional otherwise never null.
*
* @since 2.19
*/
public Optional<JsonNode> optional(String propertyName) { return Optional.empty(); }

/**
* This method is similar to {@link #get(String)}, except
* that instead of returning null if no such value exists (due
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,14 @@ public JsonNode get(int index) {
@Override
public JsonNode get(String fieldName) { return null; }

/**
* @since 2.19
*/
@Override
public Optional<JsonNode> optional(int index) {
return Optional.ofNullable(get(index));
}

@Override
public JsonNode path(String fieldName) { return MissingNode.getInstance(); }

Expand Down
12 changes: 10 additions & 2 deletions src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,12 @@ public JsonNode get(String propertyName) {
return _children.get(propertyName);
}

/**
* @since 2.19
*/
@Override
public Iterator<String> fieldNames() {
return _children.keySet().iterator();
public Optional<JsonNode> optional(String propertyName) {
return Optional.ofNullable(get(propertyName));
}

@Override
Expand All @@ -312,6 +315,11 @@ public JsonNode required(String propertyName) {
return _reportRequiredViolation("No value for property '%s' of `ObjectNode`", propertyName);
}

@Override
public Iterator<String> fieldNames() {
return _children.keySet().iterator();
}

/**
* Method to use for accessing all properties (with both names
* and values) of this JSON Object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public void testDirectCreation() throws Exception
assertFalse(n.fieldNames().hasNext());
assertNull(n.get("x")); // not used with arrays
assertTrue(n.path("x").isMissingNode());
assertFalse(n.optional("x").isPresent());
assertSame(text, n.get(0));

// single element, so:
Expand Down Expand Up @@ -439,12 +440,16 @@ public void testSimpleArray() throws Exception
// plus see that we can access stuff
assertEquals(NullNode.instance, result.path(0));
assertEquals(NullNode.instance, result.get(0));
assertEquals(NullNode.instance, result.optional(0).get());
assertEquals(BooleanNode.FALSE, result.path(1));
assertEquals(BooleanNode.FALSE, result.get(1));
assertEquals(BooleanNode.FALSE, result.optional(1).get());
assertEquals(2, result.size());

assertNull(result.get(-1));
assertNull(result.get(2));
assertFalse(result.optional(-1).isPresent());
assertFalse(result.optional(2).isPresent());
JsonNode missing = result.path(2);
assertTrue(missing.isMissingNode());
assertTrue(result.path(-100).isMissingNode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,73 @@ public void testArrayWithDefaultTyping() throws Exception
assertEquals(2, obj.path("a").asInt());
}

// [databind#2145]
@Test
public void testOptionalAccessorOnArray() throws Exception {
ArrayNode arrayNode = MAPPER.createArrayNode();
arrayNode.add("firstElement");
assertTrue(arrayNode.optional(0).isPresent());
assertEquals("firstElement", arrayNode.optional(0).get().asText());
assertFalse(arrayNode.optional(1).isPresent());
assertFalse(arrayNode.optional(-1).isPresent());
assertFalse(arrayNode.optional(999).isPresent());
assertFalse(arrayNode.optional("anyField").isPresent());
}

@Test
public void testOptionalAccessorOnObject() throws Exception {
ObjectNode objectNode = MAPPER.createObjectNode();
objectNode.put("existingField", "value");
assertTrue(objectNode.optional("existingField").isPresent());
assertEquals("value", objectNode.optional("existingField").get().asText());
assertFalse(objectNode.optional("missingField").isPresent());
assertFalse(objectNode.optional(0).isPresent());
assertFalse(objectNode.optional(-1).isPresent());
}

@Test
public void testOptionalAccessorOnNumbers() throws Exception
{
// Test IntNode
IntNode intNode = IntNode.valueOf(42);
assertFalse(intNode.optional("anyField").isPresent());
assertFalse(intNode.optional(0).isPresent());

// Test LongNode
LongNode longNode = LongNode.valueOf(123456789L);
assertFalse(longNode.optional("anyField").isPresent());
assertFalse(longNode.optional(0).isPresent());

// Test DoubleNode
DoubleNode doubleNode = DoubleNode.valueOf(3.14);
assertFalse(doubleNode.optional("anyField").isPresent());
assertFalse(doubleNode.optional(0).isPresent());

// Test DecimalNode
DecimalNode decimalNode = DecimalNode.valueOf(new java.math.BigDecimal("12345.6789"));
assertFalse(decimalNode.optional("anyField").isPresent());
assertFalse(decimalNode.optional(0).isPresent());
}

@Test
public void testOptionalAccessorOnOtherTypes() throws Exception
{
// Test TextNode
TextNode textNode = TextNode.valueOf("sampleText");
assertFalse(textNode.optional("anyField").isPresent());
assertFalse(textNode.optional(0).isPresent());

// Test NullNode
NullNode nullNode = NullNode.getInstance();
assertFalse(nullNode.optional("anyField").isPresent());
assertFalse(nullNode.optional(0).isPresent());

// Test BooleanNode
BooleanNode booleanNode = BooleanNode.TRUE;
assertFalse(booleanNode.optional("anyField").isPresent());
assertFalse(booleanNode.optional(0).isPresent());
}

// [databind#4867]
@Test
public void testAsOptional() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public void testBasics()
assertTrue(n.properties().isEmpty());
assertFalse(n.fieldNames().hasNext());
assertNull(n.get("a"));
assertFalse(n.optional("a").isPresent());
assertTrue(n.path("a").isMissingNode());

TextNode text = TextNode.valueOf("x");
Expand Down Expand Up @@ -249,6 +250,7 @@ public void testNullChecking()
JsonNode n = o1.get("x");
assertNotNull(n);
assertSame(n, NullNode.instance);
assertEquals(NullNode.instance, o1.optional("x").get());

o1.put("str", (String) null);
n = o1.get("str");
Expand Down

0 comments on commit 9e63036

Please sign in to comment.