Json Pointer implementation (#2898)

* Added Json Pointer implementation

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Fixed docs typo

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Added tracedQuery to JsonPointer

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Moved Json Pointer docs and applied some changes from code review

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Renamed build in toString() and buildURI in toURI()

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Did some doc changes

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Reworked the JsonPointer Javadoc

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Added JsonPointer#append(JsonPointer)

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

* Removed generics

Signed-off-by: slinkydeveloper <francescoguard@gmail.com>

(cherry picked from commit d9cf8c016e)
Signed-off-by: slinkydeveloper <francescoguard@gmail.com>
This commit is contained in:
Francesco Guardiani
2019-06-13 16:40:05 +02:00
committed by Julien Viet
parent 4bea87e149
commit 4da9b2ff2c
9 changed files with 1238 additions and 3 deletions

View File

@@ -774,6 +774,8 @@ include::eventbus.adoc[]
include::override/json.adoc[]
include::json-pointers.adoc[]
include::buffers.adoc[]
include::net.adoc[]

View File

@@ -0,0 +1,20 @@
== Json Pointers
Vert.x provides an implementation of <a href="https://tools.ietf.org/html/rfc6901">Json Pointers from RFC6901</a>.
You can use pointers both for querying and for writing. You can build your {@link io.vertx.core.json.pointer.JsonPointer} using
a string, a URI or manually appending paths:
[source,java]
----
{@link examples.JsonPointerExamples#example1Pointers}
----
After instantiating your pointer, use {@link io.vertx.core.json.pointer.JsonPointer#queryJson(java.lang.Object)} to query
a JSON value. You can update a Json Value using {@link io.vertx.core.json.pointer.JsonPointer#writeJson(java.lang.Object, java.lang.Object)}:
[source,java]
----
{@link examples.JsonPointerExamples#example2Pointers}
----
You can use Vert.x Json Pointer with any object model by providing a custom implementation of {@link io.vertx.core.json.pointer.JsonPointerIterator}

View File

@@ -14,8 +14,10 @@ package docoverride.json;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.pointer.JsonPointer;
import io.vertx.docgen.Source;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
@@ -80,7 +82,4 @@ public class Examples {
Boolean boolVal = array.getBoolean(2);
}
}

View File

@@ -0,0 +1,31 @@
package examples;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.pointer.JsonPointer;
import java.net.URI;
public class JsonPointerExamples {
public void example1Pointers() {
// Build a pointer from a string
JsonPointer pointer1 = JsonPointer.from("/hello/world");
// Build a pointer manually
JsonPointer pointer2 = JsonPointer.create()
.append("hello")
.append("world");
}
public void example2Pointers(JsonPointer objectPointer, JsonObject jsonObject, JsonPointer arrayPointer, JsonArray jsonArray) {
// Query a JsonObject
Object result1 = objectPointer.queryJson(jsonObject);
// Query a JsonArray
Object result2 = arrayPointer.queryJson(jsonArray);
// Write starting from a JsonObject
objectPointer.writeJson(jsonObject, "new element");
// Write starting from a JsonObject
arrayPointer.writeJson(jsonArray, "new element");
}
}

View File

@@ -0,0 +1,241 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.json.pointer;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.json.pointer.impl.JsonPointerImpl;
import java.net.URI;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Implementation of <a href="https://tools.ietf.org/html/rfc6901">RFC6901 Json Pointers</a>.
*
* @author Francesco Guardiani <a href="https://slinkydeveloper.github.io/">@slinkydeveloper</a>
*/
@VertxGen
public interface JsonPointer {
/**
* Return {@code true} if the pointer is a root pointer
*/
boolean isRootPointer();
/**
* Return {@code true} if the pointer is local (URI with only fragment)
*/
boolean isLocalPointer();
/**
* Return {@code true} if this pointer is a parent pointer of {@code child}.
* <br/>
* For instance {@code "/properties"} pointer is parent pointer of {@code "/properties/parent"}
*
* @param child
*/
boolean isParent(JsonPointer child);
/**
* Build a <a href="https://tools.ietf.org/html/rfc6901#section-5">string representation</a> of the JSON Pointer
*/
@Override
String toString();
/**
* Build a <a href="https://tools.ietf.org/html/rfc6901#section-6">URI representation</a> of the JSON Pointer
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
URI toURI();
/**
* Return the underlying URI without the fragment
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
URI getURIWithoutFragment();
/**
* Append an unescaped {@code token} to this pointer <br/>
* Note: If you provide escaped path the behaviour is undefined
*
* @param token the unescaped reference token
* @return a reference to this, so the API can be used fluently
*/
@Fluent
JsonPointer append(String token);
/**
* Append the {@code index} as reference token to JsonPointer
*
* @param index
* @return a reference to this, so the API can be used fluently
*/
@Fluent
JsonPointer append(int index);
/**
* Append an unescaped list of {@code tokens} to JsonPointer <br/>
* Note: If you provide escaped paths the behaviour is undefined
*
* @param tokens unescaped reference tokens
* @return a reference to this, so the API can be used fluently
*/
@Fluent
JsonPointer append(List<String> tokens);
/**
* Append all tokens of {@code pointer} to this pointer <br/>
* Note: The base URI of this pointer will remain untouched
*
* @param pointer other pointer
* @return a reference to this, so the API can be used fluently
*/
@Fluent
JsonPointer append(JsonPointer pointer);
/**
* Remove last reference token of this pointer
*
* @return a reference to this, so the API can be used fluently
*/
@Fluent
JsonPointer parent();
/**
* Query {@code objectToQuery} using the provided {@link JsonPointerIterator}. <br/>
* If you need to query Vert.x json data structures, use {@link JsonPointer#queryJson(Object)}<br/>
* Note: if this pointer is a root pointer, this function returns the provided object
*
* @param objectToQuery the object to query
* @param iterator the json pointer iterator that provides the logic to access to the objectToQuery
* @return null if pointer points to not existing value, otherwise the requested value
*/
default @Nullable Object query(Object objectToQuery, JsonPointerIterator iterator) { return queryOrDefault(objectToQuery, iterator, null); }
/**
* Query {@code objectToQuery} using the provided {@link JsonPointerIterator}. If the query result is null, returns the default. <br/>
* If you need to query Vert.x json data structures, use {@link JsonPointer#queryJsonOrDefault(Object, Object)}<br/>
* Note: if this pointer is a root pointer, this function returns the provided object
*
* @param objectToQuery the object to query
* @param iterator the json pointer iterator that provides the logic to access to the objectToQuery
* @param defaultValue default value if query result is null
* @return null if pointer points to not existing value, otherwise the requested value
*/
Object queryOrDefault(Object objectToQuery, JsonPointerIterator iterator, Object defaultValue);
/**
* Query {@code jsonElement}. <br/>
* Note: if this pointer is a root pointer, this function returns the provided json element
*
* @param jsonElement the json element to query
* @return null if pointer points to not existing value, otherwise the requested value
*/
default @Nullable Object queryJson(Object jsonElement) {return query(jsonElement, JsonPointerIterator.JSON_ITERATOR); }
/**
* Query {@code jsonElement}. If the query result is null, returns the default.<br/>
* Note: if this pointer is a root pointer, this function returns the provided object
*
* @param jsonElement the json element to query
* @param defaultValue default value if query result is null
* @return null if pointer points to not existing value, otherwise the requested value
*/
default @Nullable Object queryJsonOrDefault(Object jsonElement, Object defaultValue) {return queryOrDefault(jsonElement, JsonPointerIterator.JSON_ITERATOR, defaultValue); }
/**
* Query {@code objectToQuery} tracing each element walked during the query, including the first and the result (if any).<br/>
* The first element of the list is objectToQuery and the last is the result, or the element before the first null was encountered
*
* @param objectToQuery the object to query
* @param iterator the json pointer iterator that provides the logic to access to the objectToQuery
* @return the stream of walked elements
*/
List<Object> tracedQuery(Object objectToQuery, JsonPointerIterator iterator);
/**
* Write {@code newElement} in {@code objectToWrite} using this pointer. The path token "-" is handled as append to end of array <br/>
* If you need to write in Vert.x json data structures, use {@link JsonPointer#writeJson(Object, Object)} (Object)}<br/>
*
* @param objectToWrite object to write
* @param iterator the json pointer iterator that provides the logic to access to the objectToMutate
* @param newElement object to insert
* @param createOnMissing create objects when missing a object key or an array index
* @return a reference to objectToWrite if the write was completed, a reference to newElement if the pointer is a root pointer, null if the write failed
*/
Object write(Object objectToWrite, JsonPointerIterator iterator, Object newElement, boolean createOnMissing);
/**
* Write {@code newElement} in {@code jsonElement} using this pointer. The path token "-" is handled as append to end of array.
*
* @param jsonElement json element to query and write
* @param newElement json to insert
* @return a reference to json if the write was completed, a reference to newElement if the pointer is a root pointer, null if the write failed
*/
default Object writeJson(Object jsonElement, Object newElement) { return writeJson(jsonElement, newElement, false); }
/**
* Write {@code newElement} in {@code jsonElement} using this pointer. The path token "-" is handled as append to end of array.
*
* @param jsonElement json to query and write
* @param newElement json to insert
* @param createOnMissing create JsonObject when missing a object key or an array index
* @return a reference to json if the write was completed, a reference to newElement if the pointer is a root pointer, null if the write failed
*/
default Object writeJson(Object jsonElement, Object newElement, boolean createOnMissing) {
return write(jsonElement, JsonPointerIterator.JSON_ITERATOR, newElement, createOnMissing);
}
/**
* Copy a JsonPointer
*
* @return a copy of this pointer
*/
JsonPointer copy();
/**
* Build an empty JsonPointer
*
* @return a new empty JsonPointer
*/
static JsonPointer create() {
return new JsonPointerImpl();
}
/**
* Build a JsonPointer from a json pointer string
*
* @param pointer the string representing a pointer
* @return new instance of JsonPointer
* @throws IllegalArgumentException if the pointer provided is not valid
*/
static JsonPointer from(String pointer) {
return new JsonPointerImpl(pointer);
}
/**
* Build a JsonPointer from a URI.
*
* @param uri uri representing a json pointer
* @return new instance of JsonPointer
* @throws IllegalArgumentException if the pointer provided is not valid
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
static JsonPointer fromURI(URI uri) {
return new JsonPointerImpl(uri);
}
}

View File

@@ -0,0 +1,100 @@
package io.vertx.core.json.pointer;
import io.vertx.codegen.annotations.Nullable;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.json.pointer.impl.JsonPointerIteratorImpl;
/**
* The JsonPointerIterator is used by the read/write algorithms of the {@link JsonPointer} to read/write the querying data structure <br/>
*
* Every method takes the currentValue as parameter, representing the actual value held by the query algorithm.<br/>
*
* Implementations of this interface should be stateless, so they can be reused<br/>
*
* You can implement this interface to query the structure you want using json pointers
*
* @author Francesco Guardiani <a href="https://slinkydeveloper.github.io/">@slinkydeveloper</a>
*
*/
@VertxGen
public interface JsonPointerIterator {
/**
* @param currentValue
* @return {@code true} if the current value is a queryable object
*/
boolean isObject(@Nullable Object currentValue);
/**
* @param currentValue
* @return {@code true} if the current value is a queryable array
*/
boolean isArray(@Nullable Object currentValue);
/**
* @param currentValue
* @return {@code true} if the current value is null/empty
*/
boolean isNull(@Nullable Object currentValue);
/**
* @param currentValue
* @param key object key
* @return {@code true} if current value is a queryable object that contains the specified key
*/
boolean objectContainsKey(@Nullable Object currentValue, String key);
/**
* Returns the object parameter with specified key.
*
* @param currentValue
* @param key object key
* @param createOnMissing If the current value is an object that doesn't contain the key, put an empty object at provided key
* @return the requested object parameter, or null if the method was not able to find it
*/
Object getObjectParameter(@Nullable Object currentValue, String key, boolean createOnMissing);
/**
* Move the iterator the the array element at specified index
*
* @param currentValue
* @param i array index
* @return the request array element, or null if the method was not able to find it
*/
Object getArrayElement(@Nullable Object currentValue, int i);
/**
* Write object parameter at specified key
*
* @param currentValue
* @param key
* @param value
* @return true if the operation is successful
*/
boolean writeObjectParameter(@Nullable Object currentValue, String key, @Nullable Object value);
/**
* Write array element at specified index
*
* @param currentValue
* @param i
* @param value
* @return true if the operation is successful
*/
boolean writeArrayElement(@Nullable Object currentValue, int i, @Nullable Object value);
/**
* Append array element
*
* @param currentValue
* @param value
* @return true if the operation is successful
*/
boolean appendArrayElement(@Nullable Object currentValue, @Nullable Object value);
/**
* Instance of a JsonPointerIterator to query Vert.x Json structures
*/
JsonPointerIterator JSON_ITERATOR = new JsonPointerIteratorImpl();
}

View File

@@ -0,0 +1,294 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.json.pointer.impl;
import io.vertx.core.json.pointer.JsonPointer;
import io.vertx.core.json.pointer.JsonPointerIterator;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Francesco Guardiani @slinkydeveloper
*/
public class JsonPointerImpl implements JsonPointer {
final public static Pattern VALID_POINTER_PATTERN = Pattern.compile("^(/(([^/~])|(~[01]))*)*$");
URI startingUri;
// Empty means a pointer to root
List<String> decodedTokens;
public JsonPointerImpl(URI uri) {
this.startingUri = removeFragment(uri);
this.decodedTokens = parse(uri.getFragment());
}
public JsonPointerImpl(String pointer) {
this.startingUri = URI.create("#");
this.decodedTokens = parse(pointer);
}
public JsonPointerImpl() {
this.startingUri = URI.create("#");
this.decodedTokens = parse(null);
}
protected JsonPointerImpl(URI startingUri, List<String> decodedTokens) {
this.startingUri = startingUri;
this.decodedTokens = new ArrayList<>(decodedTokens);
}
private ArrayList<String> parse(String pointer) {
if (pointer == null || "".equals(pointer)) {
return new ArrayList<>();
}
if (VALID_POINTER_PATTERN.matcher(pointer).matches()) {
return Arrays
.stream(pointer.split("\\/", -1))
.skip(1) //Ignore first element
.map(this::unescape)
.collect(Collectors.toCollection(ArrayList::new));
} else
throw new IllegalArgumentException("The provided pointer is not a valid JSON Pointer");
}
private String escape(String path) {
return path.replace("~", "~0")
.replace("/", "~1");
}
private String unescape(String path) {
return path.replace("~1", "/") // https://tools.ietf.org/html/rfc6901#section-4
.replace("~0", "~");
}
@Override
public boolean isRootPointer() {
return decodedTokens.size() == 0;
}
@Override
public boolean isLocalPointer() {
return startingUri == null || startingUri.getSchemeSpecificPart() == null || startingUri.getSchemeSpecificPart().isEmpty();
}
@Override
public boolean isParent(JsonPointer c) {
JsonPointerImpl child = (JsonPointerImpl) c;
return child != null &&
(child.getURIWithoutFragment() == null && this.getURIWithoutFragment() == null || child.getURIWithoutFragment().equals(this.getURIWithoutFragment())) &&
decodedTokens.size() < child.decodedTokens.size() &&
IntStream.range(0, decodedTokens.size())
.mapToObj(i -> this.decodedTokens.get(i).equals(child.decodedTokens.get(i)))
.reduce(Boolean::logicalAnd).orElse(true);
}
@Override
public String toString() {
if (isRootPointer())
return "";
else
return "/" + String.join("/", decodedTokens.stream().map(this::escape).collect(Collectors.toList()));
}
@Override
public URI toURI() {
if (isRootPointer()) {
return replaceFragment(this.startingUri, "");
} else
return replaceFragment(
this.startingUri,
"/" + String.join("/", decodedTokens.stream().map(this::escape).collect(Collectors.toList()))
);
}
@Override
public URI getURIWithoutFragment() {
return startingUri;
}
@Override
public JsonPointer append(String path) {
decodedTokens.add(path);
return this;
}
@Override
public JsonPointer append(int i) {
return this.append(Integer.toString(i));
}
@Override
public JsonPointer append(List<String> paths) {
decodedTokens.addAll(paths);
return this;
}
@Override
public JsonPointer append(JsonPointer pointer) {
decodedTokens.addAll(((JsonPointerImpl)pointer).decodedTokens);
return this;
}
@Override
public JsonPointer parent() {
if (!this.isRootPointer()) decodedTokens.remove(decodedTokens.size() - 1);
return this;
}
@Override
public JsonPointer copy() {
return new JsonPointerImpl(this.startingUri, this.decodedTokens);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JsonPointerImpl that = (JsonPointerImpl) o;
return Objects.equals(startingUri, that.startingUri) &&
Objects.equals(decodedTokens, that.decodedTokens);
}
@Override
public int hashCode() {
return Objects.hash(startingUri, decodedTokens);
}
@Override
public Object queryOrDefault(Object value, JsonPointerIterator iterator, Object defaultValue) {
// I should threat this as a special condition because the empty string can be a json obj key!
if (isRootPointer())
return (iterator.isNull(value)) ? defaultValue : value;
else {
value = walkTillLastElement(value, iterator, false, null);
String lastKey = decodedTokens.get(decodedTokens.size() - 1);
if (iterator.isObject(value)) {
Object finalValue = iterator.getObjectParameter(value, lastKey, false);
return (!iterator.isNull(finalValue)) ? finalValue : defaultValue;
} else if (iterator.isArray(value) && !"-".equals(lastKey)) {
try {
Object finalValue = iterator.getArrayElement(value, Integer.parseInt(lastKey));
return (!iterator.isNull(finalValue)) ? finalValue : defaultValue;
} catch (NumberFormatException e) {
return defaultValue;
}
} else
return defaultValue;
}
}
@Override
public List<Object> tracedQuery(Object objectToQuery, JsonPointerIterator iterator) {
List<Object> list = new ArrayList<>();
if (isRootPointer() && !iterator.isNull(objectToQuery))
list.add(objectToQuery);
else {
Object lastValue = walkTillLastElement(objectToQuery, iterator, false, list::add);
if (!iterator.isNull(lastValue))
list.add(lastValue);
String lastKey = decodedTokens.get(decodedTokens.size() - 1);
if (iterator.isObject(lastValue)) {
lastValue = iterator.getObjectParameter(lastValue, lastKey, false);
} else if (iterator.isArray(lastValue) && !"-".equals(lastKey)) {
try {
lastValue = iterator.getArrayElement(lastValue, Integer.parseInt(lastKey));
} catch (NumberFormatException e) { }
}
if (!iterator.isNull(lastValue))
list.add(lastValue);
}
return list;
}
@Override
public Object write(Object valueToWrite, JsonPointerIterator iterator, Object newElement, boolean createOnMissing) {
if (isRootPointer()) {
return iterator.isNull(valueToWrite) ? null : newElement;
} else {
Object walkedValue = walkTillLastElement(valueToWrite, iterator, createOnMissing, null);
if (writeLastElement(walkedValue, iterator, newElement))
return valueToWrite;
else
return null;
}
}
private Object walkTillLastElement(Object value, JsonPointerIterator iterator, boolean createOnMissing, Consumer<Object> onNewValue) {
for (int i = 0; i < decodedTokens.size() - 1; i++) {
String k = decodedTokens.get(i);
if (i == 0 && "".equals(k)) {
continue; // Avoid errors with root empty string
} else if (iterator.isObject(value)) {
if (onNewValue != null) onNewValue.accept(value);
value = iterator.getObjectParameter(value, k, createOnMissing);
} else if (iterator.isArray(value)) {
if (onNewValue != null) onNewValue.accept(value);
try {
value = iterator.getArrayElement(value, Integer.parseInt(k));
if (iterator.isNull(value) && createOnMissing) {
value = iterator.getObjectParameter(value, k, true);
}
} catch (NumberFormatException e) {
value = null;
}
} else {
return null;
}
}
return value;
}
private boolean writeLastElement(Object valueToWrite, JsonPointerIterator iterator, Object newElement) {
String lastKey = decodedTokens.get(decodedTokens.size() - 1);
if (iterator.isObject(valueToWrite)) {
return iterator.writeObjectParameter(valueToWrite, lastKey, newElement);
} else if (iterator.isArray(valueToWrite)) {
if ("-".equals(lastKey)) { // Append to end
return iterator.appendArrayElement(valueToWrite, newElement);
} else { // We have a index
try {
return iterator.writeArrayElement(valueToWrite, Integer.parseInt(lastKey), newElement);
} catch (NumberFormatException e) {
return false;
}
}
} else
return false;
}
private URI removeFragment(URI oldURI) {
return replaceFragment(oldURI, null);
}
private URI replaceFragment(URI oldURI, String fragment) {
try {
if (oldURI != null) {
return new URI(oldURI.getScheme(), oldURI.getSchemeSpecificPart(), fragment);
} else return new URI(null, null, fragment);
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}
}

View File

@@ -0,0 +1,92 @@
package io.vertx.core.json.pointer.impl;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.pointer.JsonPointerIterator;
import java.util.List;
import java.util.Map;
public class JsonPointerIteratorImpl implements JsonPointerIterator {
@Override
public boolean isObject(Object value) {
return value instanceof JsonObject;
}
@Override
public boolean isArray(Object value) {
return value instanceof JsonArray;
}
@Override
public boolean isNull(Object value) {
return value == null;
}
@Override
public boolean objectContainsKey(Object value, String key) {
return isObject(value) && ((JsonObject)value).containsKey(key);
}
@Override
public Object getObjectParameter(Object value, String key, boolean createOnMissing) {
if (isObject(value)) {
if (!objectContainsKey(value, key)) {
if (createOnMissing) {
writeObjectParameter(value, key, new JsonObject());
} else {
return null;
}
}
return jsonifyValue(((JsonObject) value).getValue(key));
}
return null;
}
@Override
public Object getArrayElement(Object value, int i) {
if (isArray(value)) {
try {
return jsonifyValue(((JsonArray)value).getValue(i));
} catch (IndexOutOfBoundsException ignored) {}
}
return null;
}
@Override
public boolean writeObjectParameter(Object value, String key, Object el) {
if (isObject(value)) {
((JsonObject)value).put(key, el);
return true;
} else return false;
}
@SuppressWarnings("unchecked")
@Override
public boolean writeArrayElement(Object value, int i, Object el) {
if (isArray(value)) {
try {
((JsonArray)value).getList().add(i, el);
return true;
} catch (IndexOutOfBoundsException e) {
return false;
}
} else return false;
}
@Override
public boolean appendArrayElement(Object value, Object el) {
if (isArray(value)) {
((JsonArray)value).add(el);
return true;
} else return false;
}
@SuppressWarnings("unchecked")
private Object jsonifyValue(Object v) {
if (v instanceof Map) return new JsonObject((Map<String, Object>)v);
else if (v instanceof List) return new JsonArray((List)v);
else return v;
}
}

View File

@@ -0,0 +1,456 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.json.pointer.impl;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.pointer.JsonPointer;
import io.vertx.core.json.pointer.JsonPointerIterator;
import org.junit.Test;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* @author Francesco Guardiani @slinkydeveloper
*/
public class JsonPointerTest {
@Test
public void testParsing() {
JsonPointer pointer = JsonPointer.from("/hello/world");
assertEquals("/hello/world", pointer.toString());
}
@Test(expected = IllegalArgumentException.class)
public void testParsingErrorWrongFirstElement() {
JsonPointer.from("bla/hello/world");
}
@Test
public void testEncodingParsing() {
JsonPointer pointer = JsonPointer.create().append("hell/o").append("worl~d");
assertEquals("/hell~1o/worl~0d", pointer.toString());
}
@Test
public void testURIParsing() {
JsonPointer pointer = JsonPointer.fromURI(URI.create("http://www.example.org#/hello/world"));
assertEquals("/hello/world", pointer.toString());
assertEquals(URI.create("http://www.example.org#/hello/world"), pointer.toURI());
}
@Test
public void testURIEncodedParsing() {
JsonPointer pointer = JsonPointer.fromURI(URI.create("http://www.example.org#/hello/world/%5Ea"));
assertEquals("/hello/world/^a", pointer.toString());
assertEquals(URI.create("http://www.example.org#/hello/world/%5Ea"), pointer.toURI());
}
@Test
public void testURIJsonPointerEncodedParsing() {
JsonPointer pointer = JsonPointer.fromURI(URI.create("http://www.example.org#/hell~1o/worl~0d"));
assertEquals("/hell~1o/worl~0d", pointer.toString());
assertEquals(URI.create("http://www.example.org#/hell~1o/worl~0d"), pointer.toURI());
}
@Test
public void testBuilding() {
List<String> keys = new ArrayList<>();
keys.add("hello");
keys.add("world");
JsonPointer pointer = new JsonPointerImpl(URI.create("#"), keys);
assertEquals("/hello/world", pointer.toString());
}
@Test
public void testURIBuilding() {
JsonPointer pointer = JsonPointer.create().append("hello").append("world");
assertEquals(URI.create("#/hello/world"), pointer.toURI());
}
@Test
public void testEmptyBuilding() {
JsonPointer pointer = JsonPointer.create();
assertEquals("", pointer.toString());
assertEquals(URI.create("#"), pointer.toURI());
}
@Test
public void testAppendOtherPointer() {
JsonPointer firstPointer = JsonPointer.fromURI(URI.create("http://example.com/stuff.json#/hello")).append("world");
JsonPointer otherPointer = JsonPointer.fromURI(URI.create("http://example.com/other.json#/francesco"));
firstPointer.append(otherPointer);
assertEquals(URI.create("http://example.com/stuff.json#/hello/world/francesco"), firstPointer.toURI());
}
@Test
public void testNullQuerying() {
JsonPointer pointer = JsonPointer.from("/hello/world");
assertNull(pointer.queryJson(null));
}
@Test
public void testNullQueryingRootPointer() {
JsonPointer pointer = JsonPointer.create();
assertNull(pointer.queryJson(null));
}
@Test
public void testNullQueryingRootPointerDefault() {
JsonPointer pointer = JsonPointer.create();
assertEquals(1, pointer.queryJsonOrDefault(null, 1));
}
@Test
public void testJsonObjectQuerying() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
JsonPointer pointer = JsonPointer.from("/hello/world");
assertEquals(1, pointer.queryJson(obj));
}
@Test
public void testJsonObjectQueryingDefaultValue() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
JsonPointer pointer = JsonPointer.from("/hello/world/my/friend");
assertEquals(1, pointer.queryJsonOrDefault(obj, 1));
}
@Test
public void testJsonArrayQuerying() {
JsonArray array = new JsonArray();
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 2).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
assertEquals(1, JsonPointer.from("/1/hello/world").queryJson(array));
assertEquals(1, JsonPointer.fromURI(URI.create("#/1/hello/world")).queryJson(array));
}
@Test
public void testJsonArrayQueryingOrDefault() {
JsonArray array = new JsonArray();
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 2).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
assertEquals(1, JsonPointer.from("/5/hello/world").queryJsonOrDefault(array, 1));
}
@Test
public void testRootPointer() {
JsonPointer pointer = JsonPointer.create();
JsonArray array = new JsonArray();
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 2).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
array.add(obj);
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
assertEquals(array, pointer.queryJson(array));
assertEquals(obj, pointer.queryJson(obj));
assertEquals("hello", pointer.queryJson("hello"));
}
@Test
public void testRootPointerWrite() {
JsonPointer pointer = JsonPointer.create();
JsonObject obj = new JsonObject();
JsonArray arr = new JsonArray();
assertSame(arr, pointer.writeJson(obj, arr, false));
}
@Test
public void testWrongUsageOfDashForQuerying() {
JsonArray array = new JsonArray();
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 2).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
array.add(new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
));
JsonPointer pointer = JsonPointer.from("/-/hello/world");
assertNull(pointer.queryJson(array));
}
/*
The following JSON strings evaluate to the accompanying values:
"" // 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
*/
@Test
public void testRFCExample() {
JsonObject obj = new JsonObject(" {\n" +
" \"foo\": [\"bar\", \"baz\"],\n" +
" \"\": 0,\n" +
" \"a/b\": 1,\n" +
" \"c%d\": 2,\n" +
" \"e^f\": 3,\n" +
" \"g|h\": 4,\n" +
" \"i\\\\j\": 5,\n" +
" \"k\\\"l\": 6,\n" +
" \" \": 7,\n" +
" \"m~n\": 8\n" +
" }");
assertEquals(obj, JsonPointer.from("").queryJson(obj));
assertEquals(obj.getJsonArray("foo"), JsonPointer.from("/foo").queryJson(obj));
assertEquals(obj.getJsonArray("foo").getString(0), JsonPointer.from("/foo/0").queryJson(obj));
assertEquals(obj.getInteger(""), JsonPointer.from("/").queryJson(obj));
assertEquals(obj.getInteger("a/b"), JsonPointer.from("/a~1b").queryJson(obj));
assertEquals(obj.getInteger("c%d"), JsonPointer.from("/c%d").queryJson(obj));
assertEquals(obj.getInteger("e^f"), JsonPointer.from("/e^f").queryJson(obj));
assertEquals(obj.getInteger("g|h"), JsonPointer.from("/g|h").queryJson(obj));
assertEquals(obj.getInteger("i\\\\j"), JsonPointer.from("/i\\\\j").queryJson(obj));
assertEquals(obj.getInteger("k\\\"l"), JsonPointer.from("/k\\\"l").queryJson(obj));
assertEquals(obj.getInteger(" "), JsonPointer.from("/ ").queryJson(obj));
assertEquals(obj.getInteger("m~n"), JsonPointer.from("/m~0n").queryJson(obj));
}
@Test
public void testWriteJsonObject() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(obj, JsonPointer.from("/hello/francesco").writeJson(obj, toInsert));
assertEquals(toInsert, JsonPointer.from("/hello/francesco").queryJson(obj));
}
@Test
public void testWriteWithCreateOnMissingJsonObject() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(obj, JsonPointer.from("/hello/users/francesco").write(obj, JsonPointerIterator.JSON_ITERATOR, toInsert, true));
assertEquals(toInsert, JsonPointer.from("/hello/users/francesco").queryJson(obj));
}
@Test
public void testWriteJsonObjectOverride() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(obj, JsonPointer.from("/hello/world").writeJson(obj, toInsert));
assertEquals(toInsert, JsonPointer.from("/hello/world").queryJson(obj));
}
@Test
public void testWriteJsonArray() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", new JsonObject()).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
JsonArray array = new JsonArray();
array.add(obj.copy());
array.add(obj.copy());
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(array, JsonPointer.from("/0/hello/world/francesco").writeJson(array, toInsert));
assertEquals(toInsert, JsonPointer.from("/0/hello/world/francesco").queryJson(array));
assertNotEquals(array.getValue(0), array.getValue(1));
}
@Test
public void testWriteJsonArrayAppend() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
JsonArray array = new JsonArray();
array.add(obj.copy());
array.add(obj.copy());
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(array, JsonPointer.from("/-").writeJson(array, toInsert));
assertEquals(toInsert, JsonPointer.from("/2").queryJson(array));
assertEquals(array.getValue(0), array.getValue(1));
}
@Test
public void testWriteJsonArraySubstitute() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
JsonArray array = new JsonArray();
array.add(obj.copy());
array.add(obj.copy());
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(array, JsonPointer.from("/0").writeJson(array, toInsert));
assertEquals(toInsert, JsonPointer.from("/0").queryJson(array));
assertNotEquals(array.getValue(0), array.getValue(1));
}
@Test
public void testNestedWriteJsonArraySubstitute() {
JsonObject obj = new JsonObject()
.put("hello",
new JsonObject().put("world", 1).put("worl", "wrong")
).put("helo",
new JsonObject().put("world", "wrong").put("worl", "wrong")
);
JsonArray array = new JsonArray();
array.add(obj.copy());
array.add(obj.copy());
JsonObject root = new JsonObject().put("array", array);
Object toInsert = new JsonObject().put("github", "slinkydeveloper");
assertEquals(root, JsonPointer.from("/array/0").writeJson(root, toInsert));
assertEquals(toInsert, JsonPointer.from("/array/0").queryJson(root));
}
@Test
public void testIsParent() {
JsonPointer parent = JsonPointer.fromURI(URI.create("yaml/valid/refs/Circular.yaml#/properties"));
JsonPointer child = JsonPointer.fromURI(URI.create("yaml/valid/refs/Circular.yaml#/properties/parent"));
assertTrue(parent.isParent(child));
assertFalse(child.isParent(parent));
}
@Test
public void testIsParentDifferentURI() {
JsonPointer parent = JsonPointer.fromURI(URI.create("yaml/valid/refs/Circular.yaml#/properties"));
JsonPointer child = JsonPointer.fromURI(URI.create("json/valid/refs/Circular.yaml#/properties/parent"));
assertFalse(parent.isParent(child));
assertFalse(child.isParent(parent));
}
@Test
public void testIsParentWithRootPointer() {
JsonPointer parent = JsonPointer.fromURI(URI.create("yaml/valid/refs/Circular.yaml#"));
JsonPointer child = JsonPointer.fromURI(URI.create("yaml/valid/refs/Circular.yaml#/properties/parent"));
assertTrue(parent.isParent(child));
assertFalse(child.isParent(parent));
}
@Test
public void testTracedQuery() {
JsonObject child2 = new JsonObject().put("child3", 1);
JsonArray child1 = new JsonArray().add(child2);
JsonObject root = new JsonObject().put("child1", child1);
JsonPointer pointer = JsonPointer
.create()
.append("child1")
.append("0")
.append("child3");
List<Object> traced = pointer.tracedQuery(root, JsonPointerIterator.JSON_ITERATOR);
assertEquals(4, traced.size());
assertSame(root, traced.get(0));
assertSame(child1, traced.get(1));
assertSame(child2, traced.get(2));
assertEquals(1, traced.get(3));
}
@Test
public void testEmptyTracedQuery() {
JsonPointer pointer = JsonPointer
.create()
.append("child1")
.append("0")
.append("child3");
List<Object> traced = pointer.tracedQuery(null, JsonPointerIterator.JSON_ITERATOR);
assertTrue(traced.isEmpty());
}
@Test
public void testNotFoundTracedQuery() {
JsonObject child2 = new JsonObject().put("child5", 1);
JsonArray child1 = new JsonArray().add(child2);
JsonObject root = new JsonObject().put("child1", child1);
JsonPointer pointer = JsonPointer
.create()
.append("child1")
.append("0")
.append("child3");
List<Object> traced = pointer.tracedQuery(root, JsonPointerIterator.JSON_ITERATOR);
assertEquals(3, traced.size());
assertSame(root, traced.get(0));
assertSame(child1, traced.get(1));
assertSame(child2, traced.get(2));
}
}