com.aerospike.documentapi.JsonPathParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aerospike-document-api Show documentation
Show all versions of aerospike-document-api Show documentation
This project provides an API which allows Aerospike CDT (Collection Data Type) objects to be accessed and mutated using JSON like syntax.
Effectively this provides what can be termed a document API as CDT objects can be used to represent JSON in the Aerospike database.
package com.aerospike.documentapi;
import com.aerospike.client.cdt.*;
import com.aerospike.documentapi.pathparts.PathPart;
import com.aerospike.documentapi.pathparts.ListPathPart;
import com.aerospike.documentapi.pathparts.MapPathPart;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class for parsing JSON paths
* Not useful outside of this package, hence package visibility
*/
public class JsonPathParser {
static final String JSON_PATH_SEPARATOR = ".";
static final String DOCUMENT_ROOT_TOKEN = "$";
// Paths should match this pattern i.e. key[index1][index2]...
static final Pattern PATH_PATTERN = Pattern.compile("^([^\\[^\\]]*)(\\[(\\d+)\\])*$");
// This pattern to extract index1,index2 ...
static final Pattern INDEX_PATTERN = Pattern.compile("(\\[(\\d+)\\])");
private final List jsonPathQueryIndications = new ArrayList<>(Arrays.asList("[*]", "..", "[?"));
// Store our representation of the individual path parts
JsonPathObject jsonPathObject = new JsonPathObject();
JsonPathParser() {
}
/**
* Turn json path as string into PathPart recommendation
*
* @param jsonString a given JSON as String
* @return List
* @throws JsonParseException a JsonParseException will be thrown in case of an error.
*/
JsonPathObject parse(String jsonString) throws JsonParseException {
if (jsonString.charAt(0) != '$') {
throw new JsonPrefixException(jsonString);
}
StringTokenizer tokenizer = new StringTokenizer(jsonString, JSON_PATH_SEPARATOR);
if (!tokenizer.nextToken().equals(DOCUMENT_ROOT_TOKEN)) {
throw new JsonPrefixException(jsonString);
}
Integer index = getFirstIndexOfAQueryIndication(jsonString);
// Query is required
if (index != null) {
/*
Split the jsonString into 2 parts:
1. A string of path parts before a query operator to fetch the smallest Json possible from Aerospike.
2. A string of the remaining JsonPath to later use for executing a JsonPath query on the fetched Json from Aerospike.
For example:
$.store.book[*].author
store.book will be fetched from Aerospike and a JsonPath book[*].author query will be executed on the fetched results from Aerospike (key = book, value = nested Json).
*/
jsonPathObject.setRequiresJsonPathQuery(true);
String aerospikePathPartsString = jsonString.substring(0, index);
String jsonPathPathPartsString = jsonString.substring(index);
jsonPathObject.setJsonPathSecondStepQuery(jsonPathPathPartsString);
tokenizer = new StringTokenizer(aerospikePathPartsString, JSON_PATH_SEPARATOR);
if (!tokenizer.nextToken().equals(DOCUMENT_ROOT_TOKEN)) {
throw new JsonPrefixException(jsonString);
}
}
while (tokenizer.hasMoreTokens()) {
parsePathPart(tokenizer.nextToken());
}
return jsonPathObject;
}
/**
* Utility internal method to process the individual path parts
* Appends the found path parts to the list of path parts already found
* Expected form of pathPart is key[index1][index2]
*
* @param pathPart pathPart to Parse.
* @throws JsonParseException a JsonParseException will be thrown in case of an error.
*/
private void parsePathPart(String pathPart) throws JsonParseException {
Matcher keyMatcher = PATH_PATTERN.matcher(pathPart);
if ((!pathPart.contains("[")) && (!pathPart.contains("]"))) {
// ignore * wildcard after a dot, its the same as ending with a .path
if (!pathPart.equals("*")) {
jsonPathObject.addPathPart(new MapPathPart(pathPart));
}
} else if (keyMatcher.find()) {
String key = keyMatcher.group(1);
jsonPathObject.addPathPart(new MapPathPart(key));
Matcher indexMatcher = INDEX_PATTERN.matcher(pathPart);
while (indexMatcher.find()) {
jsonPathObject.addPathPart(new ListPathPart(Integer.parseInt(indexMatcher.group(2))));
}
} else {
throw new JsonPathException(pathPart);
}
}
public static PathPart extractLastPathPart(List pathParts) {
return pathParts.get(pathParts.size() - 1);
}
public static PathPart extractLastPathPartAndModifyList(List pathParts) {
return pathParts.remove(pathParts.size() - 1);
}
/**
* Given a list of path parts, convert this to the list of contexts you would need
* to retrieve the JSON path represented by the list of path parts
*
* @param pathParts pathParts list to convert.
* @return An array of contexts (CTXs).
*/
public static CTX[] pathPartsToContextsArray(List pathParts) {
List contextList = new Vector<>();
for (PathPart pathPart : pathParts) {
contextList.add(pathPart.toAerospikeContext());
}
return contextList.toArray(new CTX[contextList.size()]);
}
private Integer getFirstIndexOfAQueryIndication(String jsonPath) {
return jsonPathQueryIndications.stream()
.map(jsonPath::indexOf)
.filter(index -> index > 0)
.min(Integer::compare)
.orElse(null); // in case there no match for a query indication
}
/**
* Different types of json path exception
*/
public static abstract class JsonParseException extends Exception {
String jsonString;
JsonParseException(String s) {
jsonString = s;
}
}
public static class JsonPrefixException extends JsonParseException {
JsonPrefixException(String s) {
super(s);
}
public String toString() {
return jsonString + " should start with a $";
}
}
public static class JsonPathException extends JsonParseException {
JsonPathException(String s) {
super(s);
}
public String toString() {
return jsonString + " does not match key[number] format";
}
}
public static class ListException extends JsonParseException {
ListException(String s) {
super(s);
}
public String toString() {
return "You can't append to a document root";
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy