Skip to content

Commit

Permalink
Fix #4863: add basic Stream methods in JsonNode (#4864)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder authored Dec 23, 2024
1 parent 628587e commit a7cf2f3
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 9 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ 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()`,
`forEachEntry()`

2.18.3 (not yet released)

Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -1039,6 +1041,44 @@ public Set<Map.Entry<String, JsonNode>> properties() {
return Collections.emptySet();
}

/**
* Returns a stream of all value nodes of this Node, iff
* this node is an {@code ArrayNode} or {@code ObjectNode}.
* In case of {@code Object} node, property names (keys) are not included, only values.
* For other types of nodes, returns empty stream.
*
* @since 2.19
*/
public Stream<JsonNode> valueStream() {
return ClassUtil.emptyStream();
}

/**
* Returns a stream of all value nodes 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() {
return ClassUtil.emptyStream();
}

/**
* If this node is an {@code ObjectNode}, erforms the given action for each entry
* 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
* method {@link #properties()}.
*
* @param action Action to perform for each entry
*/
public void forEachEntry(BiConsumer<? super String, ? super JsonNode> action) {
// No-op for all but ObjectNode
}

/*
/**********************************************************
/* Public API, find methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.WritableTypeId;
Expand Down Expand Up @@ -288,6 +289,11 @@ public JsonNode required(int index) {
index, _children.size());
}

@Override // @since 2.19
public Stream<JsonNode> valueStream() {
return _children.stream();
}

@Override
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -54,6 +55,10 @@ protected ContainerNode(JsonNodeFactory nc) {
@Override
public abstract JsonNode get(String fieldName);

// Both ArrayNode and ObjectNode must re-implement
@Override // @since 2.19
public abstract Stream<JsonNode> valueStream();

@Override
protected abstract ObjectNode _withObject(JsonPointer origPtr,
JsonPointer currentPtr,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.WritableTypeId;
Expand Down Expand Up @@ -329,7 +331,22 @@ public Iterator<Map.Entry<String, JsonNode>> fields() {
public Set<Map.Entry<String, JsonNode>> properties() {
return _children.entrySet();
}


@Override // @since 2.19
public Stream<JsonNode> valueStream() {
return _children.values().stream();
}

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

@Override // @since 2.19
public void forEachEntry(BiConsumer<? super String, ? super JsonNode> action) {
_children.forEach(action);
}

@Override
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
{
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonGenerator;
Expand All @@ -21,7 +22,7 @@ public final class ClassUtil
private final static Annotation[] NO_ANNOTATIONS = new Annotation[0];
private final static Ctor[] NO_CTORS = new Ctor[0];

private final static Iterator<?> EMPTY_ITERATOR = Collections.emptyIterator();
private final static Iterator<Object> EMPTY_ITERATOR = Collections.emptyIterator();

/*
/**********************************************************
Expand All @@ -37,6 +38,16 @@ public static <T> Iterator<T> emptyIterator() {
return (Iterator<T>) EMPTY_ITERATOR;
}

/**
* @since 2.19
*/
public static <T> Stream<T> emptyStream() {
// Looking at its implementation, seems there ought to be simpler/more
// efficient way to create and return a shared singleton but... no luck
// so far. So just use this for convenience for now:
return Stream.empty();
}

/*
/**********************************************************
/* Methods that deal with inheritance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.fasterxml.jackson.databind.node;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.ArrayList;

import org.junit.jupiter.api.Test;
Expand All @@ -13,8 +14,6 @@
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import com.fasterxml.jackson.databind.util.RawValue;

import static java.util.Arrays.asList;

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

/**
Expand All @@ -24,7 +23,7 @@ public class ArrayNodeTest
extends DatabindTestUtil
{
@Test
public void testDirectCreation() throws IOException
public void testDirectCreation() throws Exception
{
ArrayNode n = new ArrayNode(JsonNodeFactory.instance);

Expand Down Expand Up @@ -108,7 +107,7 @@ public void testDirectCreation() throws IOException
}

@Test
public void testDirectCreation2() throws IOException
public void testDirectCreation2() throws Exception
{
JsonNodeFactory f = objectMapper().getNodeFactory();
ArrayList<JsonNode> list = new ArrayList<>();
Expand Down Expand Up @@ -139,7 +138,7 @@ public void testDirectCreation2() throws IOException
}

@Test
public void testArraySet() throws IOException {
public void testArraySet() throws Exception {
final ArrayNode array = JsonNodeFactory.instance.arrayNode();
for (int i = 0; i < 20; i++) {
array.add("Original Data");
Expand Down Expand Up @@ -267,7 +266,7 @@ public void testAddAllWithNullInCollection()
final ArrayNode array = JsonNodeFactory.instance.arrayNode();

// test
array.addAll(asList(null, JsonNodeFactory.instance.objectNode()));
array.addAll(Arrays.asList(null, JsonNodeFactory.instance.objectNode()));

// assertions
assertEquals(2, array.size());
Expand Down Expand Up @@ -479,4 +478,28 @@ public void testSimpleMismatch() throws Exception
verifyException(e, "from Integer value (token `JsonToken.VALUE_NUMBER_INT`)");
}
}

// [databind#4863]: valueStream(), entryStream(), forEachEntry()
@Test
public void testStreamMethods()
{
ObjectMapper mapper = objectMapper();
ArrayNode arr = mapper.createArrayNode();
arr.add(1).add("foo");
JsonNode n1 = arr.get(0);
JsonNode n2 = arr.get(1);

// First, valueStream() testing
assertEquals(2, arr.valueStream().count());
assertEquals(Arrays.asList(n1, n2),
arr.valueStream().collect(Collectors.toList()));

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

// And then empty forEachEntry()
arr.forEachEntry((k, v) -> { throw new UnsupportedOperationException(); });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public void testBoolean() throws Exception
// also, equality should work ok
assertEquals(result, BooleanNode.valueOf(true));
assertEquals(result, BooleanNode.getTrue());

assertNonContainerStreamMethods(f);
}

@Test
Expand Down Expand Up @@ -92,6 +94,8 @@ public void testBinary() throws Exception

assertEquals("AAMD", new BinaryNode(data).asText());
assertNodeNumbersForNonNumeric(n);

assertNonContainerStreamMethods(n2);
}

@Test
Expand All @@ -110,6 +114,8 @@ public void testPOJO()
assertNodeNumbersForNonNumeric(n);
// but if wrapping actual number, use it
assertNodeNumbers(new POJONode(Integer.valueOf(123)), 123, 123.0);

assertNonContainerStreamMethods(n);
}

// [databind#743]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,16 @@ protected void assertNodeNumbers(JsonNode n, int expInt, double expDouble)

assertTrue(n.isEmpty());
}

// Testing for non-ContainerNode (ValueNode) stream method implementations
//
// @since 2.19
protected void assertNonContainerStreamMethods(ValueNode n)
{
assertEquals(0, n.valueStream().count());
assertEquals(0, n.entryStream().count());

// And then empty forEachEntry()
n.forEachEntry((k, v) -> { throw new UnsupportedOperationException(); });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public void testBasicsWithNullNode() throws Exception
assertFalse(n.has(3));

assertNodeNumbersForNonNumeric(n);

assertNonContainerStreamMethods(n);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public void testShort()
assertTrue(ShortNode.valueOf((short) 0).canConvertToLong());
assertTrue(ShortNode.valueOf(Short.MAX_VALUE).canConvertToLong());
assertTrue(ShortNode.valueOf(Short.MIN_VALUE).canConvertToLong());

assertNonContainerStreamMethods(n);
}

@Test
Expand Down Expand Up @@ -101,6 +103,7 @@ public void testInt()
assertTrue(IntNode.valueOf(Integer.MAX_VALUE).canConvertToLong());
assertTrue(IntNode.valueOf(Integer.MIN_VALUE).canConvertToLong());

assertNonContainerStreamMethods(n);
}

@Test
Expand Down Expand Up @@ -130,6 +133,8 @@ public void testLong()
assertTrue(LongNode.valueOf(0L).canConvertToLong());
assertTrue(LongNode.valueOf(Long.MAX_VALUE).canConvertToLong());
assertTrue(LongNode.valueOf(Long.MIN_VALUE).canConvertToLong());

assertNonContainerStreamMethods(n);
}

@Test
Expand Down Expand Up @@ -193,6 +198,8 @@ public void testDouble() throws Exception
n = (DoubleNode) num;
assertEquals(-0.0, n.doubleValue());
assertEquals("-0.0", String.valueOf(n.doubleValue()));

assertNonContainerStreamMethods(n);
}

@Test
Expand Down Expand Up @@ -264,6 +271,8 @@ public void testFloat()
assertTrue(FloatNode.valueOf(0L).canConvertToLong());
assertTrue(FloatNode.valueOf(Integer.MAX_VALUE).canConvertToLong());
assertTrue(FloatNode.valueOf(Integer.MIN_VALUE).canConvertToLong());

assertNonContainerStreamMethods(n);
}

@Test
Expand Down Expand Up @@ -322,6 +331,8 @@ public void testDecimalNode() throws Exception

// also, equality should work ok
assertEquals(result, DecimalNode.valueOf(value));

assertNonContainerStreamMethods(n);
}

@Test
Expand Down Expand Up @@ -391,6 +402,8 @@ public void testBigIntegerNode() throws Exception
assertTrue(BigIntegerNode.valueOf(BigInteger.ZERO).canConvertToLong());
assertTrue(BigIntegerNode.valueOf(BigInteger.valueOf(Long.MAX_VALUE)).canConvertToLong());
assertTrue(BigIntegerNode.valueOf(BigInteger.valueOf(Long.MIN_VALUE)).canConvertToLong());

assertNonContainerStreamMethods(n);
}

@Test
Expand Down
Loading

0 comments on commit a7cf2f3

Please sign in to comment.