edu.byu.hbll.box.internal.util.JsonUtils Maven / Gradle / Ivy
package edu.byu.hbll.box.internal.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.byu.hbll.json.JsonField;
import edu.byu.hbll.json.ObjectMapperFactory;
import edu.byu.hbll.json.UncheckedObjectMapper;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.Map;
/** Json utilities. */
public class JsonUtils {
private static final UncheckedObjectMapper mapper = ObjectMapperFactory.newUnchecked();
/**
* Binds values in the map to the given {@link JsonNode}. The key in the map is the name of the
* variable in the node. The value in the map replaces the ${VARIABLE_NAME} in the node.
*
* @param node the node to modify
* @param values the values to bind to the node
*/
public static void bind(JsonNode node, Map values) {
int i = 0;
for (JsonField field : new JsonField(node)) {
JsonNode value = field.getValue();
if (value.isTextual()) {
String textValue = value.asText(null);
if (textValue != null && textValue.startsWith("${") && textValue.endsWith("}")) {
String key = textValue.substring(2, textValue.length() - 1);
JsonNode finalValue = values.getOrDefault(key, value);
if (node.isArray()) {
((ArrayNode) node).remove(i);
((ArrayNode) node).insert(i, finalValue);
} else if (node.isObject()) {
((ObjectNode) node).set(field.getKey(), finalValue);
}
}
} else if (value.isContainerNode()) {
bind(value, values);
}
i++;
}
}
/**
* Binds the given {@link JsonNode} to the provided object.
*
* @param node the values to bind
* @param object values are bound to this object
*/
public static void bind(JsonNode node, Object object) {
try {
mapper.readerForUpdating(object).readValue(node);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Deserializes the string to a {@link JsonNode}.
*
* @param value the string to deserialize
* @return the deserialized node
*/
public static JsonNode deserialize(String value) {
return mapper.readTree(value);
}
/**
* Return the found {@link JsonNode} after following the dot notation path.
*
* @param node the subect
* @param dotPath the path
* @return the found node or missing node if none found
*/
public static JsonNode dotPath(JsonNode node, String dotPath) {
String[] path = dotPath.split("\\.");
JsonNode currentNode = node;
for (String fieldName : path) {
currentNode = currentNode.path(fieldName);
}
return currentNode;
}
/**
* Serializes the document to a string.
*
* @param document the document to serialize.
* @return the serialized string
*/
public static String serialize(Object document) {
return mapper.writeValueAsString(document);
}
/**
* Converts an object to a {@link JsonNode}.
*
* @param value the object to convert
* @return the resulting {@link JsonNode}
*/
public static JsonNode toJsonNode(Object value) {
return mapper.valueToTree(value);
}
/**
* Projects the given fields of the given node onto a new node. An empty fields projects the
* entire document.
*
* Note: projection follows MongoDB's projection pattern
* (https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/). The projection
* is only applied to child nodes of objects. Arrays or nested arrays are iterated over and
* projection picks up again when objects are found. Primitive values that are not part of a
* terminal projection are removed.
*
* @param node the subject of the projection
* @param fields dot-notated fields denoting what should be projected
* @return the projected node
*/
public static JsonNode project(JsonNode node, Collection fields) {
if (fields == null || fields.isEmpty()) {
return node.deepCopy();
}
if (!node.isContainerNode()) {
return mapper.missingNode();
}
return projectRecurse(node, createProjection(fields));
}
/**
* Puts the projection fields into a json structure.
*
* @param fields the dot-notated fields denoting what should be projected
* @return a json document that corresponds to the projection
*/
private static JsonNode createProjection(Collection fields) {
ObjectNode projection = mapper.createObjectNode();
for (String field : fields) {
String[] path = field.split("\\.");
ObjectNode parent = projection;
for (String step : path) {
parent = parent.with(step);
}
}
return projection;
}
/**
* Recurses through the node and its corresponding projection. Removes fields that are not part of
* the projection. Does nothing if the projection is empty.
*
* Note: projection follows MongoDB's projection pattern
* (https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/). The projection
* is only applied to child nodes of objects. Arrays or nested arrays are iterated over and
* projection picks up again when objects are found. Primitive values that are not part of a
* terminal projection are removed.
*
* @param node the subject of the projection
* @param projection the projection
*/
private static JsonNode projectRecurse(JsonNode node, JsonNode projection) {
if (projection.isMissingNode()) {
return mapper.missingNode();
} else if (projection.size() == 0) {
return node.deepCopy();
} else if (node.isValueNode()) {
return mapper.missingNode();
} else if (node.isArray()) {
ArrayNode array = mapper.createArrayNode();
new JsonField(node)
.stream()
.map(f -> projectRecurse(f.getValue(), projection))
.filter(n -> !n.isMissingNode())
.forEach(n -> array.add(n));
return array;
} else {
ObjectNode object = mapper.createObjectNode();
new JsonField(node)
.fieldNames().stream()
.map(f -> new JsonField(f, projectRecurse(node.path(f), projection.path(f))))
.filter(f -> !f.getValue().isMissingNode())
.forEach(f -> object.set(f.getKey(), f.getValue()));
return object;
}
}
/**
* Tests whether or not the given field is part of the field projection denoted by the given
* fields.
*
*
Note: projection follows MongoDB's projection pattern
* (https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/).
*
* @param field the field to test
* @param fields the projection
* @return if the field is part of the projection
*/
public static boolean matchesProjection(String field, Collection fields) {
if (fields == null || fields.isEmpty()) {
return true;
}
JsonNode projection = createProjection(fields);
for (String part : field.split("\\.")) {
projection = projection.path(part);
if (!projection.isMissingNode() && projection.size() == 0) {
return true;
}
}
return false;
}
}