Skip to content

Commit

Permalink
Merge branch '2.19' into fix-2145
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Dec 30, 2024
2 parents b2f1bd3 + 3e0d39b commit 0eddaba
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 46 deletions.
4 changes: 3 additions & 1 deletion release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ Project: jackson-databind
(requested by @nathanukey)
#4849 Not able to deserialize Enum with default typing after upgrading 2.15.4 -> 2.17.1
(reported by Kornel Zemla)
#4863: Add basic Stream support in `JsonNode`: `valueStream()`, `entryStream()`,
#4863: Add basic Stream support in `JsonNode`: `valueStream()`, `propertyStream()`,
`forEachEntry()`
#4867: Add `Optional<JsonNode> JsonNode.asOptional()` convenience method
(fix by Joo-Hyuk K)

2.18.3 (not yet released)

Expand Down
29 changes: 24 additions & 5 deletions src/main/java/com/fasterxml/jackson/databind/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,17 @@ public boolean asBoolean(boolean defaultValue) {
return defaultValue;
}

/**
* Method that will return a {@link JsonNode} wrapped in Java's {@link Optional}.
*
* @return `Optional<JsonNode>` containing this node, or {@link Optional#empty()}
* if this is a {@link MissingNode}.
* @since 2.19
*/
public Optional<JsonNode> asOptional() {
return Optional.of(this);
}

/*
/**********************************************************************
/* Public API, extended traversal (2.10) with "required()"
Expand Down Expand Up @@ -1066,9 +1077,14 @@ public Iterator<JsonNode> elements() {
}

/**
* NOTE: This method is deprecated, use {@link #properties()} instead.
*
* @return Iterator that can be used to traverse all key/value pairs for
* object nodes; empty iterator (no contents) for other types
*
* @deprecated As of 2.19, replaced by {@link #properties()}
*/
@Deprecated // since 2.19
public Iterator<Map.Entry<String, JsonNode>> fields() {
return ClassUtil.emptyIterator();
}
Expand All @@ -1077,6 +1093,7 @@ public Iterator<Map.Entry<String, JsonNode>> fields() {
* Accessor that will return properties of {@code ObjectNode}
* similar to how {@link Map#entrySet()} works;
* for other node types will return empty {@link java.util.Set}.
* Replacement for {@link JsonNode#fields()}.
*
* @return Set of properties, if this node is an {@code ObjectNode}
* ({@link JsonNode#isObject()} returns {@code true}); empty
Expand All @@ -1101,24 +1118,26 @@ public Stream<JsonNode> valueStream() {
}

/**
* Returns a stream of all value nodes of this Node, iff
* this node is an an {@code ObjectNode}.
* Returns a stream of all properties (key, value pairs) of this Node,
* iff this node is an an {@code ObjectNode}.
* For other types of nodes, returns empty stream.
*
* @since 2.19
*/
public Stream<Map.Entry<String, JsonNode>> entryStream() {
public Stream<Map.Entry<String, JsonNode>> propertyStream() {
return ClassUtil.emptyStream();
}

/**
* If this node is an {@code ObjectNode}, erforms the given action for each entry
* If this node is an {@code ObjectNode}, performs the given action for each
* property (key, value pair)
* until all entries have been processed or the action throws an exception.
* Exceptions thrown by the action are relayed to the caller.
* For other node types, no action is performed.
*<p>
* Actions are performed in the order of entries, same as order returned by
* Actions are performed in the order of properties, same as order returned by
* method {@link #properties()}.
* This is generally the document order of properties in JSON object.
*
* @param action Action to perform for each entry
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected void _serializeNonRecursive(JsonGenerator g, JsonNode node) throws IOE
{
if (node instanceof ObjectNode) {
g.writeStartObject(this, node.size());
_serializeNonRecursive(g, new IteratorStack(), node.fields());
_serializeNonRecursive(g, new IteratorStack(), node.properties().iterator());
} else if (node instanceof ArrayNode) {
g.writeStartArray(this, node.size());
_serializeNonRecursive(g, new IteratorStack(), node.elements());
Expand Down Expand Up @@ -127,7 +127,7 @@ protected void _serializeNonRecursive(JsonGenerator g, IteratorStack stack,
}
if (value instanceof ObjectNode) {
stack.push(currIt);
currIt = value.fields();
currIt = value.properties().iterator();
g.writeStartObject(value, value.size());
} else if (value instanceof ArrayNode) {
stack.push(currIt);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.node;

import java.io.IOException;
import java.util.Optional;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -71,6 +72,11 @@ public JsonNodeType getNodeType()
public boolean asBoolean(boolean defaultValue);
*/

@Override
public Optional<JsonNode> asOptional() {
return Optional.empty();
}

/*
/**********************************************************
/* Serialization: bit tricky as we don't really have a value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ protected final static class ObjectCursor
public ObjectCursor(JsonNode n, NodeCursor p)
{
super(JsonStreamContext.TYPE_OBJECT, p);
_contents = n.fields();
_contents = n.properties().iterator();
_needEntry = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,10 @@ public JsonNode required(String propertyName) {
/**
* Method to use for accessing all properties (with both names
* and values) of this JSON Object.
*
* @deprecated since 2.19 Use instead {@link #properties()}.
*/
@Deprecated // since 2.19
@Override
public Iterator<Map.Entry<String, JsonNode>> fields() {
return _children.entrySet().iterator();
Expand All @@ -346,7 +349,7 @@ public Stream<JsonNode> valueStream() {
}

@Override // @since 2.19
public Stream<Map.Entry<String, JsonNode>> entryStream() {
public Stream<Map.Entry<String, JsonNode>> propertyStream() {
return _children.entrySet().stream();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ protected void _depositSchemaProperty(ObjectNode propertiesNode, JsonNode schema
{
JsonNode props = schemaNode.get("properties");
if (props != null) {
Iterator<Entry<String, JsonNode>> it = props.fields();
while (it.hasNext()) {
Entry<String,JsonNode> entry = it.next();
for (Entry<String,JsonNode> entry : props.properties()) {
String name = entry.getKey();
if (_nameTransformer != null) {
name = _nameTransformer.transform(name);
Expand Down
18 changes: 6 additions & 12 deletions src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;

import org.junit.jupiter.api.Test;

Expand All @@ -19,17 +18,12 @@
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BaseJsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.*;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.*;

import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.*;

public class ObjectReaderTest
public class ObjectReaderTest extends DatabindTestUtil
{
private final ObjectMapper MAPPER = newJsonMapper();

Expand Down Expand Up @@ -608,7 +602,7 @@ static class CustomObjectNode extends BaseJsonNode
private final ObjectNode _delegate;

CustomObjectNode(ObjectNode delegate) {
this._delegate = delegate;
_delegate = delegate;
}

@Override
Expand All @@ -622,8 +616,8 @@ public int size() {
}

@Override
public Iterator<Entry<String, JsonNode>> fields() {
return _delegate.fields();
public Set<Map.Entry<String, JsonNode>> properties() {
return _delegate.properties();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,9 +500,9 @@ public void testStreamMethods()
arr.valueStream().collect(Collectors.toList()));

// And then entryStream() (empty)
assertEquals(0, arr.entryStream().count());
assertEquals(0, arr.propertyStream().count());
assertEquals(Arrays.asList(),
arr.entryStream().collect(Collectors.toList()));
arr.propertyStream().collect(Collectors.toList()));

// And then empty forEachEntry()
arr.forEachEntry((k, v) -> { throw new UnsupportedOperationException(); });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,27 +217,33 @@ public void testArrayWithDefaultTyping() throws Exception
assertEquals(2, obj.path("a").asInt());
}

// [databind#2145]
@Test
public void testOptionalMethodOnAllNodeTypes() throws Exception {
// Test ObjectNode
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());

// Test ArrayNode
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 TextNode
TextNode textNode = TextNode.valueOf("sampleText");
assertFalse(textNode.optional("anyField").isPresent());
assertFalse(textNode.optional(0).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());
Expand All @@ -257,6 +263,15 @@ public void testOptionalMethodOnAllNodeTypes() throws Exception {
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();
Expand All @@ -268,4 +283,37 @@ public void testOptionalMethodOnAllNodeTypes() throws Exception {
assertFalse(booleanNode.optional("anyField").isPresent());
assertFalse(booleanNode.optional(0).isPresent());
}

// [databind#4867]
@Test
public void testAsOptional() {
// Test with MissingNode
JsonNode missingNode = MissingNode.getInstance();
Optional<JsonNode> missingOptional = missingNode.asOptional();
assertFalse(missingOptional.isPresent());

// Test with ObjectNode
ObjectNode objectNode = MAPPER.createObjectNode();
Optional<JsonNode> objectOptional = objectNode.asOptional();
assertTrue(objectOptional.isPresent());
assertEquals(objectNode, objectOptional.get());

// Test with ArrayNode
ArrayNode arrayNode = MAPPER.createArrayNode();
Optional<JsonNode> arrayOptional = arrayNode.asOptional();
assertTrue(arrayOptional.isPresent());
assertEquals(arrayNode, arrayOptional.get());

// Test with TextNode
TextNode textNode = TextNode.valueOf("text");
Optional<JsonNode> textOptional = textNode.asOptional();
assertTrue(textOptional.isPresent());
assertEquals(textNode, textOptional.get());

// Test with NullNode
NullNode nullNode = NullNode.getInstance();
Optional<JsonNode> nullOptional = nullNode.asOptional();
assertTrue(nullOptional.isPresent());
assertEquals(nullNode, nullOptional.get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public void testMissingViaMapper() throws Exception
assertTrue(onode.isObject());
assertEquals(0, onode.size());
assertFalse(onode.isMissingNode()); // real node
assertTrue(onode.asOptional().isPresent());
assertNull(onode.textValue());

// how about dereferencing?
Expand All @@ -74,6 +75,7 @@ public void testMissingViaMapper() throws Exception
JsonNode dummyNode2 = dummyNode.path(98);
assertNotNull(dummyNode2);
assertTrue(dummyNode2.isMissingNode());
assertFalse(dummyNode2.asOptional().isPresent());
JsonNode dummyNode3 = dummyNode.path("field");
assertNotNull(dummyNode3);
assertTrue(dummyNode3.isMissingNode());
Expand All @@ -92,6 +94,7 @@ public void testMissingViaMapper() throws Exception
assertTrue(dummyNode.isMissingNode());
assertNull(dummyNode.get(0));
assertNull(dummyNode.get("myfield"));
assertFalse(dummyNode.asOptional().isPresent());
dummyNode2 = dummyNode.path(98);
assertNotNull(dummyNode2);
assertTrue(dummyNode2.isMissingNode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected void assertNodeNumbers(JsonNode n, int expInt, double expDouble)
protected void assertNonContainerStreamMethods(ValueNode n)
{
assertEquals(0, n.valueStream().count());
assertEquals(0, n.entryStream().count());
assertEquals(0, n.propertyStream().count());

// And then empty forEachEntry()
n.forEachEntry((k, v) -> { throw new UnsupportedOperationException(); });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void testSimpleObject() throws Exception

// Ok, then, let's traverse via extended interface
ObjectNode obNode = (ObjectNode) root;
Iterator<Map.Entry<String,JsonNode>> fit = obNode.fields();
Iterator<Map.Entry<String,JsonNode>> fit = obNode.properties().iterator();
// we also know that LinkedHashMap is used, i.e. order preserved
assertTrue(fit.hasNext());
Map.Entry<String,JsonNode> en = fit.next();
Expand Down Expand Up @@ -143,7 +143,7 @@ public void testBasics()
assertTrue(n.isEmpty());

assertFalse(n.elements().hasNext());
assertFalse(n.fields().hasNext());
assertTrue(n.properties().isEmpty());
assertFalse(n.fieldNames().hasNext());
assertNull(n.get("a"));
assertFalse(n.optional("a").isPresent());
Expand All @@ -154,7 +154,7 @@ public void testBasics()

assertEquals(1, n.size());
assertTrue(n.elements().hasNext());
assertTrue(n.fields().hasNext());
assertTrue(n.properties().iterator().hasNext());
assertTrue(n.fieldNames().hasNext());
assertSame(text, n.get("a"));
assertSame(text, n.path("a"));
Expand Down Expand Up @@ -573,9 +573,9 @@ public void testStreamMethods()
obj.valueStream().collect(Collectors.toList()));

// And then entryStream() (empty)
assertEquals(2, obj.entryStream().count());
assertEquals(2, obj.propertyStream().count());
assertEquals(new ArrayList<>(obj.properties()),
obj.entryStream().collect(Collectors.toList()));
obj.propertyStream().collect(Collectors.toList()));

// And then empty forEachEntry()
final LinkedHashMap<String,JsonNode> map = new LinkedHashMap<>();
Expand Down

0 comments on commit 0eddaba

Please sign in to comment.