All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jayway.jsonpath.JsonModel Maven / Gradle / Ivy

There is a newer version: 2.9.0
Show newest version
/*
 * 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> transformer); /** * Map the target of this {@link ObjectOps} to the provided class * @param targetClass class to convert the target object to * @param template class * @return the mapped model */ T to(Class targetClass); } /** * Operations that can be performed on Json arrays ({@link List}s) */ public interface ArrayOps { /** * Returns the operation target * @return the operation target */ List getTarget(); /** * @see List#add(Object) */ ArrayOps add(Object o); /** * @see List#addAll(java.util.Collection) */ ArrayOps addAll(Collection collection); /** * @see List#remove(int) */ ArrayOps remove(Object o); /** * @see java.util.List#size() */ int size(); /** * @see List#set(int, Object) */ ArrayOps set(int index, Object value); /** * Allows transformations of the target list. The target for this {@link ArrayOps} will be be replaced * with the {@link Object} returned by the {@link Transformer#transform(Object)} * * @param transformer the transformer to use * @return this {@link ArrayOps} */ ArrayOps transform(Transformer> transformer); ArrayOps each(Transformer transformer); /** * @see ListMappingModelReader */ ListMappingModelReader toList(); /** * @see ListMappingModelReader */ List toListOf(Class targetClass); /** * @see ListMappingModelReader */ Set toSetOf(Class targetClass); } private class DefaultObjectOps implements ObjectOps { private JsonPath jsonPath; private DefaultObjectOps(JsonPath jsonPath) { this.jsonPath = jsonPath; } @Override public Map getTarget() { return getTargetObject(jsonPath, Map.class); } @Override public boolean containsKey(String key) { return getTargetObject(jsonPath, Map.class).containsKey(key); } @Override public ObjectOps put(String key, Object value) { getTargetObject(jsonPath, Map.class).put(key, value); return this; } @Override public ObjectOps putIfAbsent(String key, Object value) { Map targetObject = getTargetObject(jsonPath, Map.class); if (!targetObject.containsKey(key)) { targetObject.put(key, value); } return this; } @Override public Object get(String key) { return getTargetObject(jsonPath, Map.class).get(key); } @Override public Integer getInteger(String key) { return ConvertUtils.toInt(get(key)); } @Override public Long getLong(String key) { return ConvertUtils.toLong(get(key)); } @Override public Double getDouble(String key) { return ConvertUtils.toDouble(get(key)); } @Override public String getString(String key) { return ConvertUtils.toString(get(key)); } @Override public ObjectOps putAll(Map map) { getTargetObject(jsonPath, Map.class).putAll(map); return this; } @Override public ObjectOps remove(String key) { getTargetObject(jsonPath, Map.class).remove(key); return this; } @Override public ObjectOps transform(Transformer> transformer) { Map targetObject = getTargetObject(jsonPath, Map.class); Object transformed = transformer.transform(targetObject); setTargetObject(jsonPath, transformed); return this; } @Override public T to(Class targetClass) { Map targetObject = getTargetObject(jsonPath, Map.class); return new DefaultMappingModelReader(targetObject).to(targetClass); } } private class DefaultArrayOps implements ArrayOps { private JsonPath jsonPath; private DefaultArrayOps(JsonPath jsonPath) { this.jsonPath = jsonPath; } @Override public List getTarget() { return getTargetObject(jsonPath, List.class); } @Override public ArrayOps add(Object o) { getTargetObject(jsonPath, List.class).add(o); return this; } @Override public ArrayOps addAll(Collection collection) { getTargetObject(jsonPath, List.class).addAll(collection); return this; } @Override public ArrayOps remove(Object o) { getTargetObject(jsonPath, List.class).remove(o); return this; } @Override public int size() { return getTargetObject(jsonPath, List.class).size(); } @Override public ArrayOps set(int index, Object value) { getTargetObject(jsonPath, List.class).set(index, value); return this; } @Override public ListMappingModelReader toList() { return new DefaultMappingModelReader(getTargetObject(jsonPath, List.class)); } @Override public ArrayOps transform(Transformer> transformer) { Object transformed = transformer.transform(getTargetObject(jsonPath, List.class)); setTargetObject(jsonPath, transformed); return this; } @Override public ArrayOps each(Transformer transformer) { List targetObject = getTargetObject(jsonPath, List.class); for(int i = 0; i < targetObject.size(); i++){ targetObject.set(i, transformer.transform(targetObject.get(i))); } return this; } @Override public List toListOf(Class targetClass) { return new DefaultMappingModelReader(getTargetObject(jsonPath, List.class)).toListOf(targetClass); } @Override public Set toSetOf(Class targetClass) { return new DefaultMappingModelReader(getTargetObject(jsonPath, List.class)).toSetOf(targetClass); } } private static class DefaultMappingModelReader implements MappingModelReader { private Object model; private DefaultMappingModelReader(Object model) { this.model = model; } @Override public ListMappingModelReader toList() { return this; } @Override public List of(Class targetClass) { return toListOf(targetClass); } @Override public List toListOf(Class targetClass) { Object modelRef = model; if (!(modelRef instanceof List)) { modelRef = asList(modelRef); } return MappingProviderFactory.createProvider().convertValue(modelRef, List.class, targetClass); } @Override public Set toSetOf(Class targetClass) { Object modelRef = model; if (!(modelRef instanceof List)) { Set setModel = new HashSet(); setModel.add(model); modelRef = setModel; } return MappingProviderFactory.createProvider().convertValue(modelRef, Set.class, targetClass); } @Override public T to(Class targetClass) { return MappingProviderFactory.createProvider().convertValue(model, targetClass); } } private static class JsonSubModel extends JsonModel { private final JsonModel parent; private final JsonPath subModelPath; private JsonSubModel(Object jsonObject, JsonProvider jsonProvider, JsonModel parent, JsonPath subModelPath) { super(jsonObject, jsonProvider); this.parent = parent; this.subModelPath = subModelPath; } } }