All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zackehh.jackson.Jive Maven / Gradle / Ivy

There is a newer version: 1.1.1
Show newest version
package com.zackehh.jackson;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.*;
import com.zackehh.jackson.scope.SafeExecution;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static com.zackehh.jackson.stream.JiveCollectors.toArrayNode;
import static com.zackehh.jackson.stream.JiveCollectors.toObjectNode;
import static java.util.Objects.requireNonNull;

/**
 * The Jive class provides various utilities for working with Jackson
 * JSON modules, specifically ArrayNode and ObjectNode values.
 *
 * The intent is to remove friction from user interaction with Jackson
 * bindings by providing just enough to make implementing new behaviour
 * easy. One example is the ${@link #stream(ArrayNode)} function which
 * allows a user to begin working with the Stream interface against their
 * JSON values.
 */
public class Jive {

    /**
     * Private constructor as this class serves only static purposes.
     */
    private Jive() { }

    /**
     * Concatenates multiple ArrayNode instances.
     *
     * The returned value is a new ArrayNode instance, rather than
     * modifying one of the input instances.
     *
     * @param nodes the ArrayNode instances to concatenate.
     * @return a new ArrayNode instance of all provided nodes.
     */
    public static ArrayNode concat(ArrayNode... nodes) {
        return arrayCollect(Arrays.stream(nodes).flatMap(Jive::stream));
    }

    /**
     * Determines whether a JsonNode exists in an ArrayNode.
     *
     * @param node the ArrayNode to search within.
     * @param value the JsonNode value to search for.
     * @return true if the JsonNode value can be found.
     */
    public static boolean contains(ArrayNode node, JsonNode value) {
        return some(node, e -> e.equals(value));
    }

    /**
     * Determines whether a JsonNode exists in an ObjectNode as
     * a value entry.
     *
     * @param node the ObjectNode to search within.
     * @param value the JsonNode value to search for.
     * @return true if the JsonNode value can be found.
     */
    public static boolean contains(ObjectNode node, JsonNode value) {
        return some(node, e -> e.getValue().equals(value));
    }

    /**
     * Returns a new ArrayNode with the first N items removed.
     *
     * This does not modify the original ArrayNode input, but
     * returns a new ArrayNode instance with the nodes added.
     *
     * @param node the node to drop from.
     * @param count the number of nodes to drop.
     * @return a new ArrayNode instance with dropped nodes.
     */
    public static ArrayNode drop(ArrayNode node, int count) {
        return transform(node, s -> s.skip(count));
    }

    /**
     * Executes a scoped function with the provided ObjectMapper.
     *
     * Any thrown IOException instances will be caught and will
     * return an empty Optional to the user. In the case of a
     * successful execution, the return value of the called block
     * will be wrapped into an Optional and returned.
     *
     * This function can be used to remove the need to manually
     * handle exceptions when calling ObjectMapper functions.
     *
     * @param mapper the ObjectMapper instance to execute with.
     * @param execution the execution implementation to call.
     * @param  the type of the block return value.
     * @return an Optional containing a potential result.
     */
    public static  Optional execute(ObjectMapper mapper, SafeExecution execution) {
        try {
            return Optional.ofNullable(execution.apply(mapper));
        } catch(IOException e) {
            return Optional.empty();
        }
    }

    /**
     * Determines whether all JsonNodes within an ArrayNode fit
     * a provided Predicate condition.
     *
     * @param node the ArrayNode to iterate through.
     * @param predicate the condition to verify matches against.
     * @return true if every JsonNode value matches the Predicate.
     */
    public static boolean every(ArrayNode node, Predicate predicate) {
        return stream(node).allMatch(predicate);
    }

    /**
     * Determines whether all entries within an ObjectNode fit
     * a provided Predicate condition.
     *
     * @param node the ObjectNode to iterate through.
     * @param predicate the condition to verify matches against.
     * @return true if every entry value matches the Predicate.
     */
    public static boolean every(ObjectNode node, Predicate> predicate) {
        return stream(node).allMatch(predicate);
    }

    /**
     * Filters values in an ArrayNode into a new ArrayNode.
     *
     * This does not modify the input ArrayNode but collects
     * the filtered values into a new ArrayNode instance.
     *
     * @param node the ArrayNode to filter through.
     * @param predicate the condition used to filter values.
     * @return an ArrayNode instance containing filtered values.
     */
    public static ArrayNode filter(ArrayNode node, Predicate predicate) {
        return transform(node, s -> s.filter(predicate));
    }

    /**
     * Filters values in an ObjectNode into a new ObjectNode.
     *
     * This does not modify the input ObjectNode but collects
     * the filtered values into a new ObjectNode instance.
     *
     * @param node the ObjectNode to filter through.
     * @param predicate the condition used to filter values.
     * @return an ObjectNode instance containing filtered values.
     */
    public static ObjectNode filter(ObjectNode node, Predicate> predicate) {
        return transform(node, s -> s.filter(predicate));
    }

    /**
     * Attempts to find an ArrayNode value matching a criteria.
     *
     * @param node the ArrayNode to search.
     * @param predicate the condition to match the values against.
     * @return a potential JsonNode matching the predicate.
     */
    public static Optional find(ArrayNode node, Predicate predicate) {
        return stream(node).filter(predicate).findFirst();
    }

    /**
     * Attempts to find an ObjectNode entry matching a criteria.
     *
     * @param node the ObjectNode to search.
     * @param predicate the condition to match the entries against.
     * @return a potential Map.Entry matching the predicate.
     */
    public static Optional> find(ObjectNode node, Predicate> predicate) {
        return stream(node).filter(predicate).findFirst();
    }

    /**
     * Returns an Set of Strings instance containing all keys of
     * the provided ObjectNode.
     *
     * @param node the ObjectNode to retrieve keys for.
     * @return a Set containing all keys.
     */
    public static Set keys(ObjectNode node) {
        return stream(node).map(Map.Entry::getKey).collect(Collectors.toSet());
    }

    /**
     * Retrieves the last value in an ArrayNode.
     *
     * If the ArrayNode is empty, a MissingNode instance
     * will be returned.
     *
     * @param node a new ArrayNode instance.
     * @return a JsonNode instance.
     */
    public static JsonNode last(ArrayNode node) {
        return node.path(node.size() - 1);
    }

    /**
     * Maps values in an ArrayNode into a new ArrayNode.
     *
     * This does not modify the original ArrayNode, rather
     * it collects the mapped values into a new ArrayNode.
     *
     * @param node the ArrayNode to map.
     * @param function the mapping function.
     * @return an ArrayNode containing the mapped values.
     */
    public static ArrayNode map(ArrayNode node, Function function) {
        return transform(node, s -> s.map(function));
    }

    /**
     * Maps entries in an ObjectNode into a new ObjectNode.
     *
     * This does not modify the original ObjectNode, rather
     * it collects the mapped entries into a new ObjectNode.
     *
     * @param node the ObjectNode to map.
     * @param function the mapping function.
     * @return an ObjectNode containing the mapped values.
     */
    public static ObjectNode map(ObjectNode node, Function, Map.Entry> function) {
        return transform(node, s -> s.map(function));
    }

    /**
     * Performs a shallow merge of multiple ObjectNodes.
     *
     * This is not a recursive merge, it will just overwrite the value
     * on the left in case of a clash.
     *
     * @param nodes the ObjectNode instances to merge.
     * @return a new ObjectNode instance of merged keys.
     */
    public static ObjectNode merge(ObjectNode... nodes) {
        return objectCollect(Arrays.stream(nodes).flatMap(Jive::stream));
    }

    /**
     * Constructs a new Jackson ArrayNode instance.
     *
     * @return a new Jackson ArrayNode instance.
     */
    public static ArrayNode newArrayNode() {
        return JsonNodeFactory.instance.arrayNode();
    }

    /**
     * Constructs a new Jackson ArrayNode instance.
     *
     * The instance is populated with the provided JsonNode instances
     * and returned back to the caller.
     *
     * @param nodes the nodes to populate with.
     * @return a newly populated ArrayNode instance.
     */
    public static ArrayNode newArrayNode(JsonNode... nodes) {
        return arrayCollect(Arrays.stream(nodes));
    }

    /**
     * Constructs a new Iterable of type JsonNode using the provided
     * input.
     *
     * If the provided input is an ArrayNode, it is returned as is due
     * to it being already iterable. Any other JsonNode is wrapped in
     * a new instance of ArrayNode before being returned.
     *
     * If the provided value is null, an empty ArrayNode is returned
     * to avoid receiving a NullPointerException.
     *
     * @param node the node to use when seeding the Iterable.
     * @return a new Iterable of type JsonNode.
     */
    public static Iterable newIterable(JsonNode node) {
        return node == null ? newArrayNode() : node.isArray() ? node : newArrayNode(node);
    }

    /**
     * Constructs a new JSON Map.Entry from a JsonNode value.
     *
     * @param key the key of this entry as a String.
     * @param value the JsonNode value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, JsonNode value) {
        return new AbstractMap.SimpleEntry<>(key, value);
    }

    /**
     * Constructs a new JSON Map.Entry from a BigDecimal value.
     *
     * @param key the key of this entry as a String.
     * @param value the BigDecimal value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, @Nonnull BigDecimal value) {
        return newJsonEntry(key, newJsonNode(requireNonNull(value)));
    }

    /**
     * Constructs a new JSON Map.Entry from a BigInteger value.
     *
     * @param key the key of this entry as a String.
     * @param value the BigInteger value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, @Nonnull BigInteger value) {
        return newJsonEntry(key, newJsonNode(requireNonNull(value)));
    }

    /**
     * Constructs a new JSON Map.Entry from a Boolean value.
     *
     * @param key the key of this entry as a String.
     * @param value the Boolean value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, boolean value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from a byte[] value.
     *
     * @param key the key of this entry as a String.
     * @param value the byte[] value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, byte[] value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from a Double value.
     *
     * @param key the key of this entry as a String.
     * @param value the Double value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, double value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from a Float value.
     *
     * @param key the key of this entry as a String.
     * @param value the Float value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, float value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from an Integer value.
     *
     * @param key the key of this entry as a String.
     * @param value the Integer value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, int value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from a Long value.
     *
     * @param key the key of this entry as a String.
     * @param value the Long value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, long value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from a Short value.
     *
     * @param key the key of this entry as a String.
     * @param value the Short value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, short value) {
        return newJsonEntry(key, newJsonNode(value));
    }

    /**
     * Constructs a new JSON Map.Entry from a String value.
     *
     * @param key the key of this entry as a String.
     * @param value the String value of this entry.
     * @return a new Map.Entry instance.
     */
    public static Map.Entry newJsonEntry(String key, @Nonnull String value) {
        return newJsonEntry(key, newJsonNode(requireNonNull(value)));
    }

    /**
     * Constructs a new JsonNode from a BigDecimal value.
     *
     * @param value the BigDecimal value of this entry.
     * @return a new JsonNode instance.
     */
    public static DecimalNode newJsonNode(@Nonnull BigDecimal value) {
        return DecimalNode.valueOf(requireNonNull(value));
    }

    /**
     * Constructs a new JsonNode from a BigInteger value.
     *
     * @param value the BigInteger value of this entry.
     * @return a new JsonNode instance.
     */
    public static BigIntegerNode newJsonNode(@Nonnull BigInteger value) {
        return BigIntegerNode.valueOf(requireNonNull(value));
    }

    /**
     * Constructs a new JsonNode from a Boolean value.
     *
     * @param value the Boolean value of this entry.
     * @return a new JsonNode instance.
     */
    public static BooleanNode newJsonNode(boolean value) {
        return BooleanNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from a byte[] value.
     *
     * @param value the byte[] value of this entry.
     * @return a new JsonNode instance.
     */
    public static BinaryNode newJsonNode(byte[] value) {
        return BinaryNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from a Double value.
     *
     * @param value the Double value of this entry.
     * @return a new JsonNode instance.
     */
    public static DoubleNode newJsonNode(double value) {
        return DoubleNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from a Float value.
     *
     * @param value the Float value of this entry.
     * @return a new JsonNode instance.
     */
    public static FloatNode newJsonNode(float value) {
        return FloatNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from an Integer value.
     *
     * @param value the Integer value of this entry.
     * @return a new JsonNode instance.
     */
    public static IntNode newJsonNode(int value) {
        return IntNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from a Long value.
     *
     * @param value the Long value of this entry.
     * @return a new JsonNode instance.
     */
    public static LongNode newJsonNode(long value) {
        return LongNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from a Short value.
     *
     * @param value the Short value of this entry.
     * @return a new JsonNode instance.
     */
    public static ShortNode newJsonNode(short value) {
        return ShortNode.valueOf(value);
    }

    /**
     * Constructs a new JsonNode from a String value.
     *
     * @param value the String value of this entry.
     * @return a new JsonNode instance.
     */
    public static TextNode newJsonNode(@Nonnull String value) {
        return TextNode.valueOf(requireNonNull(value));
    }

    /**
     * Constructs a new Jackson ObjectNode instance.
     *
     * @return a new Jackson ObjectNode instance.
     */
    public static ObjectNode newObjectNode() {
        return JsonNodeFactory.instance.objectNode();
    }

    /**
     * Constructs a new Jackson ObjectNode instance.
     *
     * The instance is populated with the provided entries and
     * returned back to the caller.
     *
     * @param entries the entries to populate with.
     * @return a newly populated ObjectNode instance.
     */
    @SafeVarargs
    public static ObjectNode newObjectNode(Map.Entry... entries) {
        return objectCollect(Arrays.stream(entries));
    }

    /**
     * Returns true if no values in the ArrayNode match the
     * provided predicate.
     *
     * @param node the ArrayNode to verify.
     * @param predicate the condition to match against.
     * @return true if no values match the condition.
     */
    public static boolean none(ArrayNode node, Predicate predicate) {
        return !some(node, predicate);
    }

    /**
     * Returns true if no entries in the ObjectNode match the
     * provided predicate.
     *
     * @param node the ObjectNode to verify.
     * @param predicate the condition to match against.
     * @return true if no entries match the condition.
     */
    public static boolean none(ObjectNode node, Predicate> predicate) {
        return !some(node, predicate);
    }

    /**
     * Returns an ObjectNode instance without the provided list
     * of keys using the provided ObjectNode instance.
     *
     * @param node the ObjectNode to omit from.
     * @param keys the keys to omit.
     * @return an ObjectNode without the omitted fields.
     */
    public static ObjectNode omit(ObjectNode node, String... keys) {
        return omit(node, new HashSet<>(Arrays.asList(keys)));
    }

    /**
     * Returns an ObjectNode instance without the provided list
     * of keys using the provided ObjectNode instance.
     *
     * @param node the ObjectNode to omit from.
     * @param keys the keys to omit.
     * @return an ObjectNode without the omitted fields.
     */
    public static ObjectNode omit(ObjectNode node, Collection keys) {
        return reject(node, e -> keys.contains(e.getKey()));
    }

    /**
     * Returns an ObjectNode instance created from the provided list
     * of keys using the provided ObjectNode instance.
     *
     * @param node the ObjectNode to pick from.
     * @param keys the keys to pick.
     * @return an ObjectNode containing the picked fields.
     */
    public static ObjectNode pick(ObjectNode node, String... keys) {
        return pick(node, new HashSet<>(Arrays.asList(keys)));
    }

    /**
     * Returns an ObjectNode instance created from the provided Set
     * of keys using the provided ObjectNode instance.
     *
     * @param node the ObjectNode to pick from.
     * @param keys the keys to pick.
     * @return an ObjectNode containing the picked fields.
     */
    public static ObjectNode pick(ObjectNode node, Collection keys) {
        return filter(node, e -> keys.contains(e.getKey()));
    }

    /**
     * Reduces an ArrayNode into a single value of type T using
     * the provided function to accumulate values.
     *
     * @param node the ArrayNode to reduce.
     * @param initial the initial accumulator state.
     * @param function the reducing function.
     * @param  the type of the result.
     * @return a new instance of type T.
     */
    public static  T reduce(ArrayNode node, T initial, BiFunction function) {
        return stream(node).reduce(initial, function, (l, r) -> r);
    }

    /**
     * Reduces an ObjectNode into a single value of type T using
     * the provided function to accumulate values.
     *
     * @param node the ObjectNode to reduce.
     * @param initial the initial accumulator state.
     * @param function the reducing function.
     * @param  the type of the result.
     * @return a new instance of type T.
     */
    public static  T reduce(ObjectNode node, T initial, BiFunction, T> function) {
        return stream(node).reduce(initial, function, (l, r) -> r);
    }

    /**
     * Filters values in an ArrayNode into a new ArrayNode.
     *
     * This does not modify the input ArrayNode but collects
     * the filtered values into a new ArrayNode instance.
     *
     * Unlike {@link #filter(ArrayNode, Predicate)} this method
     * filters out values rather than filtering them in.
     *
     * @param node the ArrayNode to filter through.
     * @param predicate the condition used to filter values.
     * @return an ArrayNode instance containing filtered values.
     */
    public static ArrayNode reject(ArrayNode node, Predicate predicate) {
        return filter(node, e -> !predicate.test(e));
    }

    /**
     * Filters values in an ObjectNode into a new ObjectNode.
     *
     * This does not modify the input ObjectNode but collects
     * the filtered values into a new ObjectNode instance.
     *
     * Unlike {@link #filter(ObjectNode, Predicate)} this method
     * filters out values rather than filtering them in.
     *
     * @param node the ObjectNode to filter through.
     * @param predicate the condition used to filter values.
     * @return an ObjectNode instance containing filtered values.
     */
    public static ObjectNode reject(ObjectNode node, Predicate> predicate) {
        return filter(node, e -> !predicate.test(e));
    }

    /**
     * Returns true if some values in the ArrayNode match the
     * provided predicate.
     *
     * @param node the ArrayNode to verify.
     * @param predicate the condition to match against.
     * @return true if some values match the condition.
     */
    public static boolean some(ArrayNode node, Predicate predicate) {
        return stream(node).anyMatch(predicate);
    }

    /**
     * Returns true if some entries in the ObjectNode match the
     * provided predicate.
     *
     * @param node the ObjectNode to verify.
     * @param predicate the condition to match against.
     * @return true if some entries match the condition.
     */
    public static boolean some(ObjectNode node, Predicate> predicate) {
        return stream(node).anyMatch(predicate);
    }

    /**
     * Creates a new Stream from the provided ArrayNode.
     *
     * The Stream is created as a serial Stream so that the caller may
     * decide to turn parallel as needed.
     *
     * @param node the ArrayNode to stream.
     * @return a new Stream of the input elements.
     */
    public static Stream stream(ArrayNode node) {
        return StreamSupport.stream(node.spliterator(), false);
    }

    /**
     * Creates a new Stream from the provided ObjectNode.
     *
     * The Stream is created as a serial Stream so that the caller may
     * decide to turn parallel as needed.
     *
     * @param node the ObjectNode to stream.
     * @return a new Stream of the input entries.
     */
    public static Stream> stream(ObjectNode node) {
        return StreamSupport.stream(((Iterable>) node::fields).spliterator(), false);
    }

    /**
     * Returns a new ArrayNode containing N taken items from a provided
     * ArrayNode instance.
     *
     * This does not modify the original ArrayNode input, but
     * returns a new ArrayNode instance with the nodes taken.
     *
     * @param node the node to take from.
     * @param count the number of nodes to take.
     * @return a new ArrayNode instance fo the taken nodes.
     */
    public static ArrayNode take(ArrayNode node, int count) {
        return transform(node, s -> s.limit(count));
    }

    /**
     * Transforms a provided ArrayNode into a new ArrayNode instance.
     *
     * The provided Function maps the base Stream to a new Stream in
     * order to transform the ArrayNode internally. The final nodes
     * are collected back into a new ArrayNode.
     *
     * @param node the ArrayNode to transform.
     * @param transformer the Stream transformer.
     * @return a new ArrayNode instance after transformation.
     */
    public static ArrayNode transform(ArrayNode node, Function, Stream> transformer) {
        return arrayCollect(transformer.apply(stream(node)));
    }

    /**
     * Transforms a provided ObjectNode into a new ObjectNode instance.
     *
     * The provided Function maps the base Stream to a new Stream in
     * order to transform the ObjectNode internally. The final entry
     * pairs are collected back into a new ObjectNode.
     *
     * @param node the ObjectNode to transform.
     * @param transformer the Stream transformer.
     * @return a new ObjectNode instance after transformation.
     */
    public static ObjectNode transform(ObjectNode node, Function>, Stream>> transformer) {
        return objectCollect(transformer.apply(stream(node)));
    }

    /**
     * Returns an ArrayNode instance with all duplicate values
     * removed.
     *
     * @param node the input ArrayNode to uniq.
     * @return a new ArrayNode of unique instance.
     */
    public static ArrayNode uniq(ArrayNode node) {
        return transform(node, Stream::distinct);
    }

    /**
     * Returns an ArrayNode instance containing all values of
     * the provided ObjectNode.
     *
     * Note that the returned values are in no guaranteed order.
     *
     * @param node the ObjectNode to retrieve values for.
     * @return an ArrayNode containing all JsonNode values.
     */
    public static ArrayNode values(ObjectNode node) {
        return arrayCollect(stream(node).map(Map.Entry::getValue));
    }

    /**
     * Collects a provided JsonNode Stream into an ArrayNode.
     *
     * @param stream the Stream instance to collect.
     * @return a new ArrayNode after collection.
     */
    private static ArrayNode arrayCollect(Stream stream) {
        return stream.collect(toArrayNode());
    }

    /**
     * Collects a provided entry Stream into an ObjectNode.
     *
     * @param stream the Stream instance to collect.
     * @return a new ObjectNode after collection.
     */
    private static ObjectNode objectCollect(Stream> stream) {
        return stream.collect(toObjectNode());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy