io.github.selcukes.databind.utils.JsonQuery Maven / Gradle / Ivy
/*
* Copyright (c) Ramesh Babu Prudhvi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.selcukes.databind.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Utility class for navigating and extracting data from JSON documents using a
* path syntax.
*/
public class JsonQuery {
private final JsonNode rootNode;
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* Constructs a JsonQuery instance from a JSON string.
*
* @param json the JSON string
*/
@SneakyThrows
public JsonQuery(String json) {
this.rootNode = objectMapper.readTree(json);
}
/**
* Constructs a JsonQuery instance from a JsonNode.
*
* @param node the JsonNode to use as the root node
*/
public JsonQuery(JsonNode node) {
this.rootNode = node;
}
/**
* Factory method to create a JsonQuery instance from a JSON string.
*
* @param json the JSON string
* @return a JsonQuery instance
*/
public static JsonQuery from(String json) {
return new JsonQuery(json);
}
/**
* Retrieves a single value from the JSON document at the specified path.
*
* @param path the path to the value
* @param type the class type of the value to retrieve
* @param the type of the value
* @return the value at the specified path, or null if not found
*/
@SneakyThrows
public T get(String path, Class type) {
JsonNode node = getNode(path);
return (node == null || node.isMissingNode()) ? null : objectMapper.treeToValue(node, type);
}
@SneakyThrows
public List getList(String path, Class type) {
return getList(path).stream().map(node -> objectMapper.convertValue(node, type)).toList();
}
/**
* Retrieves a list of JsonNode values from the JSON document at the
* specified wildcard path.
*
* @param path the wildcard path to retrieve values from
* @return a list of JsonNode found at the specified path
*/
public List getList(String path) {
List values = new ArrayList<>(); // Updated to List
JsonNode currentNode = rootNode;
String[] keys = path.split("\\.");
for (String key : keys) {
if (containsWildcard(key)) {
return extractValuesFromArray(currentNode, keys, key, values);
} else {
currentNode = navigateToNode(currentNode, key);
if (currentNode.isMissingNode()) {
return values; // Return empty if missing node
}
}
}
return extractFinalValues(currentNode, values);
}
/**
* Retrieves a JsonNode from the specified path.
*
* @param path the path to the node
* @return the JsonNode at the specified path, or null if not found
*/
public JsonNode getNode(String path) {
JsonNode currentNode = rootNode;
String[] keys = path.split("\\.");
for (String key : keys) {
currentNode = navigateToNode(currentNode, key);
if (currentNode == null || currentNode.isMissingNode()) {
return null;
}
}
return currentNode;
}
/**
* Extracts values from arrays using wildcard paths.
*
* @param currentNode the current JSON node
* @param keys the path keys
* @param key the current key being processed
* @param values the list to store extracted values
* @return a list of extracted JsonNode values
*/
private List extractValuesFromArray(
JsonNode currentNode,
String[] keys,
String key,
List values
) {
String arrayKey = extractArrayKey(key);
var resultNode = currentNode.path(arrayKey);
if (resultNode.isArray()) {
for (JsonNode node : resultNode) {
processArrayElement(keys, arrayKey, node, values);
}
}
return values;
}
/**
* Processes each element in the array and extracts the required values.
*
* @param keys the path keys
* @param arrayKey the current array key
* @param node the current JSON node being processed
* @param values the list to store extracted values
*/
private void processArrayElement(String[] keys, String arrayKey, JsonNode node, List values) {
if (isLastKey(keys, arrayKey)) {
values.add(node);
} else {
JsonNode resultNode = getNodeFromSubPath(node, removeProcessedArrayKey(keys, arrayKey));
if (resultNode != null) {
values.add(resultNode);
}
}
}
/**
* Checks if the specified key contains a wildcard.
*
* @param key the key to check
* @return true if the key contains a wildcard, false otherwise
*/
private boolean containsWildcard(String key) {
return key.contains("[*]");
}
/**
* Extracts the key before the wildcard in a given key.
*
* @param key the key containing a wildcard
* @return the extracted key before the wildcard
*/
private String extractArrayKey(String key) {
return key.substring(0, key.indexOf("[*]"));
}
/**
* Removes the processed array key part from the path.
*
* @param keys the path keys
* @param arrayKey the current array key
* @return the remaining path as a string
*/
private String removeProcessedArrayKey(String[] keys, String arrayKey) {
var joinedKeys = String.join(".", keys);
var pattern = arrayKey + "[*].";
int index = joinedKeys.indexOf(pattern);
return (index != -1) ? joinedKeys.substring(index + pattern.length()) : joinedKeys;
}
/**
* Checks if the specified key is the last key in the path.
*
* @param keys the path keys
* @param key the key to check
* @return true if it is the last key, false otherwise
*/
private boolean isLastKey(String[] keys, String key) {
return keys[keys.length - 1].equals(key);
}
/**
* Extracts values from the final node, which may be an array or a single
* value.
*
* @param currentNode the current JSON node
* @param values the list to store extracted values
* @return a list of extracted JsonNode values
*/
private List extractFinalValues(JsonNode currentNode, List values) {
if (currentNode.isArray()) {
for (JsonNode node : currentNode) {
values.add(node);
}
} else if (currentNode.isValueNode()) {
values.add(currentNode);
}
return values;
}
/**
* Retrieves a node from a sub-path within a given JSON node.
*
* @param node the JSON node to search within
* @param subPath the sub-path to search for
* @return the resulting JsonNode or null if not found
*/
private JsonNode getNodeFromSubPath(JsonNode node, String subPath) {
String[] keys = subPath.split("\\.");
JsonNode currentNode = node;
for (String key : keys) {
currentNode = navigateToNode(currentNode, key);
if (currentNode == null || currentNode.isMissingNode()) {
return null;
}
}
return currentNode;
}
/**
* Navigates to the correct node by parsing the path.
*
* @param currentNode the current JSON node
* @param key the key to navigate
* @return the resulting JsonNode
*/
private JsonNode navigateToNode(JsonNode currentNode, String key) {
if (isArrayKey(key)) {
return Objects.requireNonNull(getArrayNode(currentNode, key));
}
return currentNode.path(key);
}
/**
* Checks if the key indicates an array access.
*
* @param key the key to check
* @return true if the key indicates an array, false otherwise
*/
private boolean isArrayKey(String key) {
return key.contains("[") && key.contains("]");
}
/**
* Retrieves a JsonNode from an array using the provided key and index.
*
* @param currentNode the current JSON node
* @param key the key to access the array
* @return the JsonNode at the specified array index
*/
private JsonNode getArrayNode(JsonNode currentNode, String key) {
String arrayKey = key.substring(0, key.indexOf("["));
int index = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));
var arrayNode = currentNode.path(arrayKey);
return arrayNode.isArray() && arrayNode.size() > index ? arrayNode.get(index) : null;
}
/**
* Converts a JsonNode to a string representation.
*
* @param node the JsonNode to convert
* @return the string representation of the JsonNode
*/
@SneakyThrows
public String toString(JsonNode node) {
return objectMapper.writeValueAsString(node);
}
/**
* Converts the root node of the JSON to a string representation.
*
* @return the string representation of the root node
*/
@SneakyThrows
public String toString() {
return toString(rootNode);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy