org.abego.commons.jsonpointer.JSONPointer Maven / Gradle / Ivy
/*
* MIT License
*
* Copyright (c) 2020 Udo Borkowski, ([email protected])
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.abego.commons.jsonpointer;
import org.abego.commons.lang.ArrayUtil;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.UnaryOperator;
import static org.abego.commons.lang.ClassUtil.classNameOrNull;
@SuppressWarnings({"HardCodedStringLiteral"})
public final class JSONPointer implements UnaryOperator<@Nullable Object> {
static final String UNSUPPORTED_FOR_ROOT = "Unsupported for root JSON Pointer";
static final char TOKEN_PREFIX = '/';
private final String pointer;
private final @NonNull String[] tokens;
private JSONPointer(String pointer) {
this.pointer = pointer;
this.tokens = toTokens(pointer);
}
/**
* Return the object given by the {@code pointer} string, relative to the {@code root}.
*
*
* JSON Pointer Examples (taken from RFC 6901)
* Given the JSON document
*
* {
* "foo": ["bar", "baz"],
* "": 0,
* "a/b": 1,
* "c%d": 2,
* "e^f": 3,
* "g|h": 4,
* "i\\j": 5,
* "k\"l": 6,
* " ": 7,
* "m~n": 8
* }
*
* The following JSON Pointers evaluate to the accompanying values:
*
* JSON Pointer Value
* "" // the whole document
* "/foo" ["bar", "baz"]
* "/foo/0" "bar"
* "/" 0
* "/a~1b" 1
* "/c%d" 2
* "/e^f" 3
* "/g|h" 4
* "/i\\j" 5
* "/k\"l" 6
* "/ " 7
* "/m~0n" 8
*
*
* @param jsonPointer a JSON Pointer, as defined in RFC 6901 (https://tools.ietf.org/html/rfc6901)
*/
@Nullable
public static Object referencedValue(Object root, String jsonPointer) {
return referencedValue(root, toTokens(jsonPointer), jsonPointer);
}
private static @Nullable Object referencedValue(@Nullable Object root, @NonNull String[] tokens, String jsonPointer) {
@Nullable Object result = root;
for (String token : tokens) {
result = processToken(result, token, jsonPointer);
}
return result;
}
private static @NonNull String[] toTokens(String jsonPointer) {
return jsonPointer.isEmpty()
? new String[0]
: nonEmptyJSONPointerToTokens(jsonPointer);
}
private static @NonNull String[] nonEmptyJSONPointerToTokens(String jsonPointer) {
if (!jsonPointer.startsWith("/"))
throw newMissingRootSlash(jsonPointer);
// separate the pointer in the individual navigational tokens
// (but skip the initial '/')
@NonNull String[] steps = jsonPointer.substring(1).split("/", -1);
@NonNull String[] tokens = new @NonNull String[steps.length];
for (int i = 0; i < steps.length; i++) {
tokens[i] = unescape(steps[i]);
}
return tokens;
}
private static String unescape(String escapedToken) {
return escapedToken.replaceAll("~1", "/").replaceAll("~0", "~");
}
@SuppressWarnings("unchecked")
private static @Nullable Object processToken(@Nullable Object data, String token, String jsonPointer) {
if (data instanceof Map) {
Map<@Nullable Object, @Nullable Object> map = (Map<@Nullable Object, @Nullable Object>) data;
if (!map.containsKey(token)) {
throw newMissingKeyException(token, jsonPointer);
}
return map.get(token);
} else if (data instanceof Object[]) {
return getArrayItem((@Nullable Object[]) data, token, jsonPointer);
} else if (data instanceof List) {
// List is also an "Iterable" but we handle it specially because
// we can use `List#get(int)` to access the list's its i-th item.
// For an iterable we need to iterate and count to find the i-th
// item. This is typically slower than the `get`.
return getListItem((List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy