com.jayway.jsonpath.JsonModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of json-path Show documentation
Show all versions of json-path Show documentation
Java port of Stefan Goessner JsonPath.
/*
* Copyright 2011 the original author or authors.
* 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 com.jayway.jsonpath;
import com.jayway.jsonpath.internal.ConvertUtils;
import com.jayway.jsonpath.internal.JsonFormatter;
import com.jayway.jsonpath.internal.PathToken;
import com.jayway.jsonpath.internal.IOUtils;
import com.jayway.jsonpath.spi.JsonProvider;
import com.jayway.jsonpath.spi.JsonProviderFactory;
import com.jayway.jsonpath.spi.MappingProviderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import static java.util.Arrays.asList;
import static org.apache.commons.lang.Validate.*;
/**
* A JsonModel holds a parsed JSON document and provides easy read and write operations. In contrast to the
* static read operations provided by {@link JsonPath} a JsonModel will only parse the document once.
*
* @author Kalle Stenflo
*/
public class JsonModel {
private static final JsonPath JSON_PATH_ROOT = JsonPath.compile("$");
private Object jsonObject;
private JsonProvider jsonProvider;
// --------------------------------------------------------
//
// Constructors
//
// --------------------------------------------------------
private JsonModel(String jsonObject, JsonProvider jsonProvider) {
notNull(jsonObject, "json can not be null");
this.jsonProvider = jsonProvider;
this.jsonObject = jsonProvider.parse(jsonObject);
}
/**
* Creates a new JsonModel based on a json document.
* Note that the jsonObject must either a {@link List} or a {@link Map}
*
* @param jsonObject the json object
* @param jsonProvider
*/
private JsonModel(Object jsonObject, JsonProvider jsonProvider) {
notNull(jsonObject, "json can not be null");
if (!jsonProvider.isContainer(jsonObject)) {
throw new IllegalArgumentException("Invalid container object");
}
this.jsonProvider = jsonProvider;
this.jsonObject = jsonObject;
}
/**
* Creates a new JsonModel based on an {@link InputStream}
*
* @param jsonInputStream the input stream
* @param jsonProvider
*/
private JsonModel(InputStream jsonInputStream, JsonProvider jsonProvider) {
notNull(jsonInputStream, "jsonInputStream can not be null");
this.jsonProvider = jsonProvider;
this.jsonObject = jsonProvider.parse(jsonInputStream);
}
/**
* Creates a new JsonModel by fetching the content from the provided URL
*
* @param jsonURL the URL to read
* @param jsonProvider
* @throws IOException failed to load URL
*/
private JsonModel(URL jsonURL, JsonProvider jsonProvider) throws IOException {
notNull(jsonURL, "jsonURL can not be null");
InputStream jsonInputStream = null;
try {
jsonInputStream = jsonURL.openStream();
this.jsonObject = jsonProvider.parse(jsonInputStream);
this.jsonProvider = jsonProvider;
} finally {
IOUtils.closeQuietly(jsonInputStream);
}
}
/**
* Check if this JsonModel is holding a JSON array as to object
*
* @return true if root is an array
*/
public boolean isList() {
return jsonProvider.isList(jsonObject);
}
/**
* Check if this JsonModel is holding a JSON object as to object
*
* @return true if root is an object
*/
public boolean isMap() {
return jsonProvider.isMap(jsonObject);
}
/**
* Prints this JsonModel to standard out
*/
public void print(){
String json = toJson();
System.out.println(JsonFormatter.prettyPrint(json));
}
/**
* Check if this JsonModel has the given definite path
*
* @see com.jayway.jsonpath.JsonPath#isPathDefinite()
*
* @param jsonPath path to check
* @return true if model contains path
*/
public boolean hasPath(String jsonPath){
return hasPath(JsonPath.compile(jsonPath));
}
/**
* Check if this JsonModel has the given definite path
*
* @see com.jayway.jsonpath.JsonPath#isPathDefinite()
*
* @param jsonPath path to check
* @return true if model contains path
*/
public boolean hasPath(JsonPath jsonPath){
isTrue(jsonPath.isPathDefinite(), "hasPath can only be used for definite paths");
try {
get(jsonPath);
} catch(InvalidPathException e){
return false;
}
return true;
}
// --------------------------------------------------------
//
// Getters
//
// --------------------------------------------------------
/**
* Returns the root object of this JsonModel
*
* @return returns the root object
*/
public Object getJsonObject() {
return this.jsonObject;
}
// --------------------------------------------------------
//
// Model readers
//
// --------------------------------------------------------
/**
* Reads the given path from this JsonModel. Filters is a way to problematically filter the contents of a list.
* Instead of writing the filter criteria directly inside the JsonPath expression the filter is indicated and
* provided as an argument.
*
* All three statements below are equivalent
*
*
* JsonModel model = JsonModel.model(myJson);
*
* //A
* List books = model.read("$store.book[?(@author == 'Nigel Rees')]");
*
* //B
* List books = model.read("$store.book[?]", filter(where("author").is("Nigel Rees"));
*
* //C
* JsonPath path = JsonPath.compile("$store.book[?]", filter(where("author").is("Nigel Rees"));
*
* List books = model.read(path);
*
*
*
* The filters are applied in the order they are provided. If a path contains multiple [?] filter markers
* the filters must be passed in the correct order.
*
* @param jsonPath the path to read
* @param filters filters to use in the path
* @param expected return type
* @return the json path result
* @see Filter
* @see Criteria
*/
@SuppressWarnings({"unchecked"})
public T get(String jsonPath, Filter... filters) {
return (T) get(JsonPath.compile(jsonPath, filters));
}
/**
* Reads the given path from this JsonModel.
*
* @param jsonPath the path to read
* @param expected return type
* @return the json path result
*/
@SuppressWarnings({"unchecked"})
public T get(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
return (T) jsonPath.read(jsonObject);
}
// --------------------------------------------------------
//
// Model writers
//
// --------------------------------------------------------
/**
* Gets an {@link ArrayOps} for this JsonModel. Note that the root element of this model
* must be a json array.
*
* @return array operations for this JsonModel
*/
public ArrayOps opsForArray() {
isTrue(jsonProvider.isList(jsonObject), "This JsonModel is not a JSON array");
return opsForArray(JSON_PATH_ROOT);
}
/**
* Gets an {@link ArrayOps} for the array inside this JsonModel identified by the given JsonPath. The path must
* be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}).
*
* Note that the element returned by the given path must be a json array.
*
* @param jsonPath definite path to array to perform operations on
* @return array operations for the targeted array
*/
public ArrayOps opsForArray(String jsonPath) {
return opsForArray(JsonPath.compile(jsonPath));
}
/**
* Gets an {@link ArrayOps} for the array inside this JsonModel identified by the given JsonPath. The path must
* be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}).
*
* Note that the element returned by the given path must be a json array.
*
* @param jsonPath definite path to array to perform operations on
* @return array operations for the targeted array
*/
public ArrayOps opsForArray(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
return new DefaultArrayOps(jsonPath);
}
/**
* Gets an {@link ObjectOps} for this JsonModel. Note that the root element of this model
* must be a json object.
*
* @return object operations for this JsonModel
*/
public ObjectOps opsForObject() {
return opsForObject(JSON_PATH_ROOT);
}
/**
* Gets an {@link ObjectOps} for the object inside this JsonModel identified by the given JsonPath. The path must
* be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}).
*
* Note that the element returned by the given path must be a json object.
*
* @param jsonPath definite path to object to perform operations on
* @return object operations for the targeted object
*/
public ObjectOps opsForObject(String jsonPath) {
return opsForObject(JsonPath.compile(jsonPath));
}
/**
* Gets an {@link ObjectOps} for the object inside this JsonModel identified by the given JsonPath. The path must
* be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}).
*
* Note that the element returned by the given path must be a json object.
*
* @param jsonPath definite path to object to perform operations on
* @return object operations for the targeted object
*/
public ObjectOps opsForObject(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
return new DefaultObjectOps(jsonPath);
}
// --------------------------------------------------------
//
// JSON extractors
//
// --------------------------------------------------------
/**
* Creates a JSON representation of this JsonModel
*
* @return model as Json
*/
public String toJson() {
return toJson(false);
}
/**
* Creates a JSON representation of this JsonModel
*
* @param prettyPrint if the model should be pretty printed
* @return
*/
public String toJson(boolean prettyPrint) {
String json = jsonProvider.toJson(jsonObject);
if(prettyPrint)
return JsonFormatter.prettyPrint(json);
else
return json;
}
/**
* Creates a JSON representation of the result of the provided JsonPath
*
* @return path result as Json
*/
public String toJson(String jsonPath, Filter... filters) {
return toJson(JsonPath.compile(jsonPath, filters));
}
/**
* Creates a JSON representation of the result of the provided JsonPath
*
* @return path result as Json
*/
public String toJson(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
return jsonProvider.toJson(get(jsonPath));
}
// --------------------------------------------------------
//
// Sub model readers
//
// --------------------------------------------------------
/**
* Returns a sub model from this JsonModel. A sub model can be any JSON object or JSON array
* addressed by a definite path. In contrast to a detached model changes on the sub model
* will be applied on the source model (the JsonModel from which the sub model was created)
*
*
* @param jsonPath the absolute path to extract a JsonModel for
* @return the new JsonModel
*
* @see com.jayway.jsonpath.JsonPath#isPathDefinite()
*/
public JsonModel getSubModel(String jsonPath) {
return getSubModel(JsonPath.compile(jsonPath));
}
/**
* Returns a sub model from this JsonModel. A sub model can be any JSON object or JSON array
* addressed by a definite path. In contrast to a detached model changes on the sub model
* will be applied on the source model (the JsonModel from which the sub model was created)
*
*
* @param jsonPath the absolute path to extract a JsonModel for
* @return the new JsonModel
*
* @see com.jayway.jsonpath.JsonPath#isPathDefinite()
*/
public JsonModel getSubModel(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
isTrue(jsonPath.isPathDefinite(), "You can only get subModels with a definite path. Use getDetachedModel if path is not definite.");
Object subModel = jsonPath.read(jsonObject);
if (!jsonProvider.isContainer(subModel)) {
throw new InvalidModelException("The path " + jsonPath.getPath() + " returned an invalid model " + (subModel != null ? subModel.getClass() : "null"));
}
return new JsonSubModel(subModel, this.jsonProvider, this, jsonPath);
}
// --------------------------------------------------------
//
// Detached sub model readers
//
// --------------------------------------------------------
/**
* Creates a detached sub model from this JsonModel. A detached sub model does not have
* to be created using a definite path. Changes on a detached sub model will not be reflected on the
* source model (the JsonModel from which the sub model was created).
*
* @param jsonPath the absolute path to extract a JsonModel for
* @param filters filters to expand the path
* @return a detached JsonModel
*/
public JsonModel getSubModelDetached(String jsonPath, Filter... filters) {
return getSubModelDetached(JsonPath.compile(jsonPath, filters));
}
/**
* Creates a detached sub model from this JsonModel. A detached sub model does not have
* to be created using a definite path. Changes on a detached sub model will not be reflected on the
* source model (the JsonModel from which the sub model was created).
*
* @param jsonPath the absolute path to extract a JsonModel for
* @return a detached JsonModel
*/
public JsonModel getSubModelDetached(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
Object subModel = jsonPath.read(jsonObject);
if (!jsonProvider.isContainer(subModel)) {
throw new InvalidModelException("The path " + jsonPath.getPath() + " returned an invalid model " + (subModel != null ? subModel.getClass() : "null"));
}
subModel = jsonProvider.clone(subModel);
return new JsonModel(subModel, this.jsonProvider);
}
// --------------------------------------------------------
//
// Mapping model readers
//
// --------------------------------------------------------
/**
* Returns a {@link MappingModelReader} for this JsonModel. Note that to use this functionality you need
* an optional dependencies on your classpath (jackson-mapper-asl ver >= 1.9.5)
*
* @return a object mapper
*/
public MappingModelReader map() {
return new DefaultMappingModelReader(this.jsonObject);
}
/**
* Returns a {@link MappingModelReader} for the JsonModel targeted by the provided {@link JsonPath}. Note that to use this functionality you need
* an optional dependencies on your classpath (jackson-mapper-asl ver >= 1.9.5)
*
* @return a object mapper
*/
public MappingModelReader map(String jsonPath, Filter... filters) {
return map(JsonPath.compile(jsonPath, filters));
}
/**
* Returns a {@link MappingModelReader} for the JsonModel targeted by the provided {@link JsonPath}. Note that to use this functionality you need
* an optional dependencies on your classpath (jackson-mapper-asl ver >= 1.9.5)
*
* @return a object mapper
*/
public MappingModelReader map(JsonPath jsonPath) {
notNull(jsonPath, "jsonPath can not be null");
return new DefaultMappingModelReader(JsonModel.this.get(jsonPath));
}
// --------------------------------------------------------
//
// Static factory methods
//
// --------------------------------------------------------
/**
* Creates a JsonModel
*
* @param json json string
* @return a new JsonModel
*/
public static JsonModel model(String json) {
notEmpty(json, "json can not be null or empty");
return new JsonModel(json, JsonProviderFactory.createProvider());
}
public static JsonModel create(String json) {
return model(json);
}
/**
* Creates a JsonModel
*
* @param jsonObject a json container (a {@link Map} or a {@link List})
* @return a new JsonModel
*/
public static JsonModel model(Object jsonObject) {
notNull(jsonObject, "jsonObject can not be null");
return new JsonModel(jsonObject, JsonProviderFactory.createProvider());
}
public static JsonModel create(Object jsonObject) {
return model(jsonObject);
}
/**
* Creates a JsonModel
*
* @param url pointing to a Json document
* @return a new JsonModel
*/
public static JsonModel model(URL url) throws IOException {
notNull(url, "url can not be null");
return new JsonModel(url, JsonProviderFactory.createProvider());
}
public static JsonModel create(URL url) throws IOException {
return model(url);
}
/**
* Creates a JsonModel
*
* @param jsonInputStream json document stream
* @return a new JsonModel
*/
public static JsonModel model(InputStream jsonInputStream) throws IOException {
notNull(jsonInputStream, "jsonInputStream can not be null");
return new JsonModel(jsonInputStream, JsonProviderFactory.createProvider());
}
public static JsonModel create(InputStream jsonInputStream) throws IOException {
return model(jsonInputStream);
}
// --------------------------------------------------------
//
// Private helpers
//
// --------------------------------------------------------
private T getTargetObject(JsonPath jsonPath, Class clazz) {
notNull(jsonPath, "jsonPath can not be null");
if (!jsonPath.isPathDefinite()) {
throw new IndefinitePathException(jsonPath.getPath());
}
JsonProvider jsonProvider = JsonProviderFactory.createProvider();
Object modelRef = jsonObject;
if (jsonPath.getTokenizer().size() == 1) {
PathToken onlyToken = jsonPath.getTokenizer().iterator().next();
if ("$".equals(onlyToken.getFragment())) {
return clazz.cast(modelRef);
}
} else {
LinkedList tokens = jsonPath.getTokenizer().getPathTokens();
PathToken currentToken;
do {
currentToken = tokens.poll();
modelRef = currentToken.apply(modelRef, jsonProvider);
} while (!tokens.isEmpty());
if (modelRef.getClass().isAssignableFrom(clazz)) {
throw new InvalidModelException(jsonPath + " does nor refer to a Map but " + currentToken.getClass().getName());
}
return clazz.cast(modelRef);
}
throw new InvalidModelException();
}
private void setTargetObject(JsonPath jsonPath, Object newValue) {
JsonPath setterPath = jsonPath.copy();
PathToken pathToken = setterPath.getTokenizer().removeLastPathToken();
if (pathToken.isRootToken()) {
if (this instanceof JsonSubModel) {
JsonSubModel thisModel = (JsonSubModel) this;
thisModel.parent.setTargetObject(thisModel.subModelPath, newValue);
} else {
this.jsonObject = newValue;
}
} else {
if (pathToken.isArrayIndexToken()) {
int arrayIndex = pathToken.getArrayIndex();
opsForArray(setterPath).set(arrayIndex, newValue);
} else {
opsForObject(setterPath).put(pathToken.getFragment(), newValue);
}
}
}
// --------------------------------------------------------
//
// Interfaces
//
// --------------------------------------------------------
/**
* Converts a {@link JsonModel} to an Object
*/
public interface ObjectMappingModelReader {
/**
* Converts this JsonModel to the specified class using the configured {@link com.jayway.jsonpath.spi.MappingProvider}
*
* @see MappingProviderFactory
*
* @param targetClass class to convert the {@link JsonModel} to
* @param template class
* @return the mapped model
*/
T to(Class targetClass);
}
/**
* Converts a {@link JsonModel} to an {@link Collection} of Objects
*/
public interface ListMappingModelReader {
/**
* Converts this JsonModel to the a list of objects with the provided class using the configured {@link com.jayway.jsonpath.spi.MappingProvider}
*
* @param targetClass class to convert the {@link JsonModel} array items to
* @param template class
* @return the mapped mode
*/
List of(Class targetClass);
/**
* Syntactic sugar function to use with {@link ListMappingModelReader#of}
*/
ListMappingModelReader toList();
/**
* Converts this JsonModel to the a {@link List} of objects with the provided class using the configured {@link com.jayway.jsonpath.spi.MappingProvider}
*
* @param targetClass class to convert the {@link JsonModel} array items to
* @param template class
* @return the mapped mode
*/
List toListOf(Class targetClass);
/**
* Converts this JsonModel to the a {@link Set} of objects with the provided class using the configured {@link com.jayway.jsonpath.spi.MappingProvider}
*
* @param targetClass class to convert the {@link JsonModel} array items to
* @param template class
* @return the mapped model
*/
Set toSetOf(Class targetClass);
}
/**
* Object mapping interface used when for root object that can be either a {@link List} or a {@link Map}.
* It's up to the invoker to know what the conversion target can be mapped to.
*/
public interface MappingModelReader extends ListMappingModelReader, ObjectMappingModelReader {
}
/**
* Operations that can be performed on Json objects ({@link Map}s)
*/
public interface ObjectOps {
/**
* Returns the operation target
* @return the operation target
*/
Map getTarget();
/**
* @see Map#containsKey(Object)
*/
boolean containsKey(String key);
/**
* @see Map#put(Object, Object)
*/
ObjectOps put(String key, Object value);
/**
* Adds the value to the target map if it is not already present
* @param key the key
* @param value the value
* @return this {@link ObjectOps}
*/
ObjectOps putIfAbsent(String key, Object value);
/**
* @see Map#get(Object)
*/
Object get(String key);
/**
* Tries to convert the value associated with the key to an {@link Integer}
* @param key the key
* @return converted value
*/
Integer getInteger(String key);
/**
* Tries to convert the value associated with the key to an {@link Long}
* @param key the key
* @return converted value
*/
Long getLong(String key);
/**
* Tries to convert the value associated with the key to an {@link Double}
* @param key the key
* @return converted value
*/
Double getDouble(String key);
/**
* Tries to convert the value associated with the key to an {@link String}
* @param key the key
* @return converted value
*/
String getString(String key);
/**
* @see Map#putAll(java.util.Map)
*/
ObjectOps putAll(Map map);
/**
* @see Map#remove(Object)
*/
ObjectOps remove(String key);
/**
* Allows transformations of the target object. the target for this {@link ObjectOps} will be be replaced
* with the {@link Object} returned by the {@link Transformer#transform(Object)}
*
* @param transformer the transformer to use
* @return this {@link ObjectOps}
*/
ObjectOps transform(Transformer