Create Java 8 Stream from ArrayNode
Asked Answered
I

3

40

Is it possible to create stream from com.fasterxml.jackson.databind.node.ArrayNode?
I tried:

ArrayNode files = (ArrayNode) json.get("files");
Stream<JsonNode> stream = Stream.of(files);

But it will actually give stream of one element, the initial ArrayNode object.
Correct result should be Stream<JsonNode>, can I achieve it?

Infestation answered 20/9, 2015 at 20:7 Comment(3)
Will the entire array here be loaded in memory? I believe the answer is "yes". So what is the point to have a stream here?Provincetown
@AndreyKarayvansky it's not about performance but about ability to use Java 8 Stream API methods to process ArrayNode collection in a "functional way" (using methods like map, filter, collect...).Infestation
Lists.newArrayList(arrayNode.elements()).stream() //by GuavaKingmaker
K
54

ArrayNode implements Iterable. Iterable has a spliterator() method. You can create a sequential Stream from a Spliterator using

ArrayNode arrayNode = (ArrayNode) json.get("xyz");
StreamSupport.stream(arrayNode.spliterator(), false)
Kordofan answered 20/9, 2015 at 20:12 Comment(2)
I tried your solution and it works but I don't understand why Stream.of(files) didn't while spliterator did.Naji
Because Stream.of() expects an array as argument, and an ArrayNode is not an array.Kordofan
H
20

An ArrayNode class provides random access: you can get size() and an element by index (using get(index)). This is all you need to create a good stream:

Stream<JsonNode> nodes = IntStream.range(0, files.size()).mapToObj(files::get);

Note that this solution is better than using default spliterator (as suggested by other answerers) as it can split well and reports the size properly. Even if you don't care about parallel processing, some operations like toArray() will work more effectively as knowing the size in advance will help to allocate an array of proper size.

Holdall answered 21/9, 2015 at 2:22 Comment(3)
I agree with all you said and this is in many ways better solution. But I would expect to see such solution directly in ArrayNode class or as some Utils method. Streams should make code easier to read and understand. Using this solution for simple filter-map-collect operation would slightly increase complexity of code. Anyway, very nice thinking, you got my +1.Infestation
@klerik, It's not so easy to provide stream API methods while maintaining pre-Java-8 compatibility.Holdall
I like this as it means I don't have to cast my JsonNode to an ArrayNode either.Neona
R
5

ArrayNode#elements returns an Iterator over it's elements you can use that to create a Stream (by leveraging StreamSupport). StreamSupport requires a Spliterator and to create a Spliterator from an Iterator you can use the Spliterators class.

  ArrayNode files = (ArrayNode) json.get("files");
  Stream<JsonNode>  elementStream = StreamSupport.stream(Spliterators
                  .spliteratorUnknownSize(files.elements(),
                        Spliterator.ORDERED),false);

cyclops-streams has a StreamUtils class has a static method that makes this a bit cleaner (I am the author).

 ArrayNode files = (ArrayNode) json.get("files");
 Stream<JsonNode>  elementStream = StreamUtils.stream(files.elements());

Taking into account @JB Nizet's answer that ArrayNode is an iterable with StreamUtils you can pass in the ArrayNode and get the Stream back directly.

Stream<JsonNode>  elementStream = StreamUtils.stream((ArrayNode) json.get("files"));
Raper answered 20/9, 2015 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.