Skip to content

Commit 9e63036

Browse files
authored
Add JsonNode.optional(String name) and optional(int index) methods (#4866)
1 parent 3e0d39b commit 9e63036

File tree

7 files changed

+141
-2
lines changed

7 files changed

+141
-2
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Project: jackson-databind
88

99
#1467: Support `@JsonUnwrapped` with `@JsonCreator`
1010
(implementation by Liam F)
11+
#2145: Add `JsonNode.optional(String name)` and `optional(int index)` methods
12+
(fix by Joo-Hyuk K)
1113
#2461: Nested `@JsonUnwrapped` property names not correctly handled
1214
(reported by @plovell)
1315
(fix contributed by @SandeepGaur2016)

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

+47
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,53 @@ public boolean isObject() {
208208
*/
209209
@Override
210210
public JsonNode get(String fieldName) { return null; }
211+
212+
/**
213+
* Method for accessing value of the specified element of
214+
* an array node, wrapped in an {@link Optional}. For other nodes,
215+
* an empty Optional is always returned.
216+
*<p>
217+
* For array nodes, index specifies
218+
* exact location within array and allows for efficient iteration
219+
* over child elements (underlying storage is guaranteed to
220+
* be efficiently indexable, i.e. has random-access to elements).
221+
* If index is less than 0, or equal-or-greater than
222+
* <code>node.size()</code>, an empty Optional is returned; no exception is
223+
* thrown for any index.
224+
*<p>
225+
* NOTE: if the element value has been explicitly set as <code>null</code>
226+
* (which is different from removal!),
227+
* a {@link com.fasterxml.jackson.databind.node.NullNode} will be returned
228+
* wrapped in an Optional, not an empty Optional.
229+
*
230+
* @return Optional containing the node that represents the value of the specified element,
231+
* if this node is an array and has the specified element and otherwise, an
232+
* empty Optional, never null.
233+
*
234+
* @since 2.19
235+
*/
236+
public Optional<JsonNode> optional(int index) { return Optional.empty(); }
237+
238+
/**
239+
* Method for accessing value of the specified field of
240+
* an object node. If this node is not an object (or it
241+
* does not have a value for specified field name), or
242+
* if there is no field with such name, empty {@link Optional}
243+
* is returned.
244+
*<p>
245+
* NOTE: if the property value has been explicitly set as <code>null</code>
246+
* (which is different from removal!), an Optional containing
247+
* {@link com.fasterxml.jackson.databind.node.NullNode} will be returned,
248+
* not null.
249+
*
250+
* @return Optional that may contain value of the specified field,
251+
* if this node is an object and has value for the specified
252+
* field. Empty Optional otherwise never null.
253+
*
254+
* @since 2.19
255+
*/
256+
public Optional<JsonNode> optional(String propertyName) { return Optional.empty(); }
257+
211258
/**
212259
* This method is similar to {@link #get(String)}, except
213260
* that instead of returning null if no such value exists (due

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

+8
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,14 @@ public JsonNode get(int index) {
269269
@Override
270270
public JsonNode get(String fieldName) { return null; }
271271

272+
/**
273+
* @since 2.19
274+
*/
275+
@Override
276+
public Optional<JsonNode> optional(int index) {
277+
return Optional.ofNullable(get(index));
278+
}
279+
272280
@Override
273281
public JsonNode path(String fieldName) { return MissingNode.getInstance(); }
274282

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

+10-2
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,12 @@ public JsonNode get(String propertyName) {
283283
return _children.get(propertyName);
284284
}
285285

286+
/**
287+
* @since 2.19
288+
*/
286289
@Override
287-
public Iterator<String> fieldNames() {
288-
return _children.keySet().iterator();
290+
public Optional<JsonNode> optional(String propertyName) {
291+
return Optional.ofNullable(get(propertyName));
289292
}
290293

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

318+
@Override
319+
public Iterator<String> fieldNames() {
320+
return _children.keySet().iterator();
321+
}
322+
315323
/**
316324
* Method to use for accessing all properties (with both names
317325
* and values) of this JSON Object.

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

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public void testDirectCreation() throws Exception
4848
assertFalse(n.fieldNames().hasNext());
4949
assertNull(n.get("x")); // not used with arrays
5050
assertTrue(n.path("x").isMissingNode());
51+
assertFalse(n.optional("x").isPresent());
5152
assertSame(text, n.get(0));
5253

5354
// single element, so:
@@ -439,12 +440,16 @@ public void testSimpleArray() throws Exception
439440
// plus see that we can access stuff
440441
assertEquals(NullNode.instance, result.path(0));
441442
assertEquals(NullNode.instance, result.get(0));
443+
assertEquals(NullNode.instance, result.optional(0).get());
442444
assertEquals(BooleanNode.FALSE, result.path(1));
443445
assertEquals(BooleanNode.FALSE, result.get(1));
446+
assertEquals(BooleanNode.FALSE, result.optional(1).get());
444447
assertEquals(2, result.size());
445448

446449
assertNull(result.get(-1));
447450
assertNull(result.get(2));
451+
assertFalse(result.optional(-1).isPresent());
452+
assertFalse(result.optional(2).isPresent());
448453
JsonNode missing = result.path(2);
449454
assertTrue(missing.isMissingNode());
450455
assertTrue(result.path(-100).isMissingNode());

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

+67
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,73 @@ public void testArrayWithDefaultTyping() throws Exception
217217
assertEquals(2, obj.path("a").asInt());
218218
}
219219

220+
// [databind#2145]
221+
@Test
222+
public void testOptionalAccessorOnArray() throws Exception {
223+
ArrayNode arrayNode = MAPPER.createArrayNode();
224+
arrayNode.add("firstElement");
225+
assertTrue(arrayNode.optional(0).isPresent());
226+
assertEquals("firstElement", arrayNode.optional(0).get().asText());
227+
assertFalse(arrayNode.optional(1).isPresent());
228+
assertFalse(arrayNode.optional(-1).isPresent());
229+
assertFalse(arrayNode.optional(999).isPresent());
230+
assertFalse(arrayNode.optional("anyField").isPresent());
231+
}
232+
233+
@Test
234+
public void testOptionalAccessorOnObject() throws Exception {
235+
ObjectNode objectNode = MAPPER.createObjectNode();
236+
objectNode.put("existingField", "value");
237+
assertTrue(objectNode.optional("existingField").isPresent());
238+
assertEquals("value", objectNode.optional("existingField").get().asText());
239+
assertFalse(objectNode.optional("missingField").isPresent());
240+
assertFalse(objectNode.optional(0).isPresent());
241+
assertFalse(objectNode.optional(-1).isPresent());
242+
}
243+
244+
@Test
245+
public void testOptionalAccessorOnNumbers() throws Exception
246+
{
247+
// Test IntNode
248+
IntNode intNode = IntNode.valueOf(42);
249+
assertFalse(intNode.optional("anyField").isPresent());
250+
assertFalse(intNode.optional(0).isPresent());
251+
252+
// Test LongNode
253+
LongNode longNode = LongNode.valueOf(123456789L);
254+
assertFalse(longNode.optional("anyField").isPresent());
255+
assertFalse(longNode.optional(0).isPresent());
256+
257+
// Test DoubleNode
258+
DoubleNode doubleNode = DoubleNode.valueOf(3.14);
259+
assertFalse(doubleNode.optional("anyField").isPresent());
260+
assertFalse(doubleNode.optional(0).isPresent());
261+
262+
// Test DecimalNode
263+
DecimalNode decimalNode = DecimalNode.valueOf(new java.math.BigDecimal("12345.6789"));
264+
assertFalse(decimalNode.optional("anyField").isPresent());
265+
assertFalse(decimalNode.optional(0).isPresent());
266+
}
267+
268+
@Test
269+
public void testOptionalAccessorOnOtherTypes() throws Exception
270+
{
271+
// Test TextNode
272+
TextNode textNode = TextNode.valueOf("sampleText");
273+
assertFalse(textNode.optional("anyField").isPresent());
274+
assertFalse(textNode.optional(0).isPresent());
275+
276+
// Test NullNode
277+
NullNode nullNode = NullNode.getInstance();
278+
assertFalse(nullNode.optional("anyField").isPresent());
279+
assertFalse(nullNode.optional(0).isPresent());
280+
281+
// Test BooleanNode
282+
BooleanNode booleanNode = BooleanNode.TRUE;
283+
assertFalse(booleanNode.optional("anyField").isPresent());
284+
assertFalse(booleanNode.optional(0).isPresent());
285+
}
286+
220287
// [databind#4867]
221288
@Test
222289
public void testAsOptional() {

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

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ public void testBasics()
146146
assertTrue(n.properties().isEmpty());
147147
assertFalse(n.fieldNames().hasNext());
148148
assertNull(n.get("a"));
149+
assertFalse(n.optional("a").isPresent());
149150
assertTrue(n.path("a").isMissingNode());
150151

151152
TextNode text = TextNode.valueOf("x");
@@ -249,6 +250,7 @@ public void testNullChecking()
249250
JsonNode n = o1.get("x");
250251
assertNotNull(n);
251252
assertSame(n, NullNode.instance);
253+
assertEquals(NullNode.instance, o1.optional("x").get());
252254

253255
o1.put("str", (String) null);
254256
n = o1.get("str");

0 commit comments

Comments
 (0)