Skip to content

Instantly share code, notes, and snippets.

@ogerardin
Created February 27, 2020 09:14
Show Gist options
  • Save ogerardin/72dfc6c338e5df624ba13aa050e737fb to your computer and use it in GitHub Desktop.
Save ogerardin/72dfc6c338e5df624ba13aa050e737fb to your computer and use it in GitHub Desktop.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Streams;
import lombok.Data;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Utility class to build a {@link Stream} of contextualized JSON nodes from a root {@link JsonNode}.
* Streamed objects are {@link NodeInfo} instances, which provide direct access to each node's container and full
* Json Path.
* This provides a way to walk a JSON tree using a Java Stream pipeline, for example:
* <pre>
* JsonNodeStream.of(rootNode)
* .filter(JsonNodeStream.NodeInfo::isField)
* .map(field -> field.getJsonPath().replaceAll("\\[\\d+\\]", "[*]"))
* .distinct()
* .forEach(System.out::println);
* </pre>
*/
public class JsonNodeStream {
public static Stream<NodeInfo> of(JsonNode node) {
return of(node, "$", null);
}
private static Stream<NodeInfo> of(JsonNode node, String jsonPath, JsonNode container) {
NodeInfo thisNodeInfo = new NodeInfo(container, jsonPath, node);
Stream<NodeInfo> result = Stream.of(thisNodeInfo);
if (node.isArray()) {
result = Streams.concat(result, ofArray((ArrayNode) node, jsonPath));
}
else if (node.isObject()) {
result = Streams.concat(result, ofObject((ObjectNode) node, jsonPath));
}
return result;
}
private static Stream<NodeInfo> ofObject(ObjectNode objectNode, String jsonPath) {
Iterable<Map.Entry<String, JsonNode>> iterable = objectNode::fields;
Stream<Map.Entry<String, JsonNode>> fieldStream = StreamSupport.stream(iterable.spliterator(), false);
Stream<NodeInfo> nodeStream = fieldStream
.map(entry -> of(entry.getValue(), jsonPath + "." + entry.getKey(), objectNode))
.flatMap(Function.identity());
return nodeStream;
}
private static Stream<NodeInfo> ofArray(ArrayNode arrayNode, String jsonPath) {
Iterable<JsonNode> iterable = arrayNode::elements;
Stream<JsonNode> elementStream = StreamSupport.stream(iterable.spliterator(), false);
//noinspection UnstableApiUsage
Stream<NodeInfo> nodeStream = Streams.mapWithIndex(elementStream,
(element, index) -> of(element, jsonPath + "[" + index + "]", arrayNode))
.flatMap(Function.identity());
return nodeStream;
}
@Data
public static class NodeInfo {
/** Node container (object or array); null if root */
final JsonNode container;
/** Full Json Path of the node (container.field if container is an object, container[index] if the container is an array*/
final String jsonPath;
/** The node */
final JsonNode node;
public boolean isField() {
return container != null && container.isObject();
}
public boolean isValue() {
return node.isValueNode();
}
@Override
public String toString() {
return "Field{" +
"container=" + (container != null ? container.getNodeType() : "null") +
", field='" + jsonPath + '\'' +
", node=" + node.getNodeType() +
'}';
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment