diff --git a/json-path/src/main/java/com/jayway/jsonpath/InvalidModelPathException.java b/json-path/src/main/java/com/jayway/jsonpath/InvalidModelPathException.java new file mode 100644 index 00000000..5d0714b4 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/InvalidModelPathException.java @@ -0,0 +1,26 @@ +package com.jayway.jsonpath; + +/** + * Created by IntelliJ IDEA. + * User: kallestenflo + * Date: 3/4/12 + * Time: 2:38 PM + */ +public class InvalidModelPathException extends RuntimeException { + + public InvalidModelPathException() { + super(); + } + + public InvalidModelPathException(String message) { + super(message); + } + + public InvalidModelPathException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidModelPathException(Throwable cause) { + super(cause); + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java b/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java index 778eb03c..48da791f 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java @@ -15,13 +15,26 @@ package com.jayway.jsonpath; import com.jayway.jsonpath.spi.JsonProviderFactory; +import org.apache.commons.io.IOUtils; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.type.CollectionType; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import static java.util.Arrays.asList; +import static org.apache.commons.lang.Validate.notEmpty; +import static org.apache.commons.lang.Validate.notNull; + /** + * A JsonModel represents a parsed JSON document that provides easy and efficient read 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 { @@ -36,75 +49,143 @@ public class JsonModel { // // -------------------------------------------------------- + /** + * Creates a new JsonModel + * + * @param json json string + */ public JsonModel(String json) { this(JsonProviderFactory.getInstance().parse(json)); } + /** + * 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 + */ private JsonModel(Object jsonObject) { + notNull(jsonObject, "json can not be null"); + + if (!(jsonObject instanceof Map) && !(jsonObject instanceof List)) { + throw new IllegalArgumentException("Invalid container object"); + } + this.jsonObject = jsonObject; } + /** + * Creates a new JsonModel based on an {@link InputStream} + * + * @param jsonInputStream the input stream + */ + private JsonModel(InputStream jsonInputStream) { + notNull(jsonInputStream, "jsonInputStream can not be null"); + + this.jsonObject = JsonProviderFactory.getInstance().parse(jsonInputStream); + } + + /** + * Creates a new JsonModel by fetching the content from the provided URL + * + * @param jsonURL the URL to read + * @throws IOException + */ + private JsonModel(URL jsonURL) throws IOException { + notNull(jsonURL, "jsonURL can not be null"); + + InputStream jsonInputStream = null; + try { + jsonInputStream = jsonURL.openStream(); + this.jsonObject = JsonProviderFactory.getInstance().parse(jsonInputStream); + } finally { + IOUtils.closeQuietly(jsonInputStream); + } + } + + // -------------------------------------------------------- + // + // Model readers + // + // -------------------------------------------------------- + @SuppressWarnings({"unchecked"}) public T get(String jsonPath) { + notEmpty(jsonPath, "jsonPath can not be null or empty"); + JsonPath path = JsonPath.compile(jsonPath); return (T) get(path); } @SuppressWarnings({"unchecked"}) public T get(JsonPath jsonPath) { + notNull(jsonPath, "jsonPath can not be null"); + return (T) jsonPath.read(jsonObject); } - public String getJson() { + + // -------------------------------------------------------- + // + // JSON extractors + // + // -------------------------------------------------------- + public String toJson() { return JsonProviderFactory.getInstance().toJson(jsonObject); } - public String getJson(String jsonPath) { + public String toJson(String jsonPath) { + notEmpty(jsonPath, "jsonPath can not be null or empty"); + return JsonProviderFactory.getInstance().toJson(get(jsonPath)); } - public String getJson(JsonPath jsonPath) { + public String toJson(JsonPath jsonPath) { + notNull(jsonPath, "jsonPath can not be null"); + return JsonProviderFactory.getInstance().toJson(get(jsonPath)); } + // -------------------------------------------------------- + // + // Sub model readers + // + // -------------------------------------------------------- public JsonModel getSubModel(String jsonPath) { + notEmpty(jsonPath, "jsonPath can not be null or empty"); + JsonPath path = JsonPath.compile(jsonPath); return getSubModel(path); } public JsonModel getSubModel(JsonPath jsonPath) { + notNull(jsonPath, "jsonPath can not be null"); + Object subModel = jsonPath.read(jsonObject); + + if(!(subModel instanceof Map) && !(subModel instanceof List)){ + throw new InvalidModelPathException("The path " + jsonPath.getPath() + " returned an invalid model " + (subModel!=null?subModel.getClass():"null")); + } + return new JsonModel(subModel); } - + // -------------------------------------------------------- + // + // Mapping model readers + // + // -------------------------------------------------------- public MappingModelReader map(final String jsonPath) { + notEmpty(jsonPath, "jsonPath can not be null or empty"); - return new MappingModelReader() { + return new DefaultMappingModelReader(JsonModel.this.get(jsonPath)); + } - private ObjectMapper objectMapper = JsonModel.getObjectMapper(); + public MappingModelReader map(final JsonPath jsonPath) { + notNull(jsonPath, "jsonPath can not be null"); - @Override - public List toListOf(Class targetClass) { - Object model = JsonModel.this.get(jsonPath); - CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(List.class, targetClass); - return objectMapper.convertValue(model, colType); - } - - @Override - public Set toSetOf(Class targetClass) { - Object model = JsonModel.this.get(jsonPath); - CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(Set.class, targetClass); - return objectMapper.convertValue(model, colType); - } - - @Override - public T to(Class targetClass) { - Object model = JsonModel.this.get(jsonPath); - return objectMapper.convertValue(model, targetClass); - } - }; + return new DefaultMappingModelReader(JsonModel.this.get(jsonPath)); } // -------------------------------------------------------- @@ -113,20 +194,38 @@ public class JsonModel { // // -------------------------------------------------------- public static JsonModel create(String json) { + notEmpty(json, "json can not be null or empty"); + return new JsonModel(json); } public static JsonModel create(Object jsonObject) { + notNull(jsonObject, "jsonObject can not be null"); + return new JsonModel(jsonObject); } + public static JsonModel create(URL url) throws IOException { + notNull(url, "url can not be null"); + + return new JsonModel(url); + } + + public static JsonModel create(InputStream jsonInputStream) throws IOException { + notNull(jsonInputStream, "jsonInputStream can not be null"); + + return new JsonModel(jsonInputStream); + } // -------------------------------------------------------- // - // Support interfaces + // Interfaces // // -------------------------------------------------------- public interface MappingModelReader { + + ListMappingModelReader toList(); + List toListOf(Class targetClass); Set toSetOf(Class targetClass); @@ -134,6 +233,57 @@ public class JsonModel { T to(Class targetClass); } + public interface ListMappingModelReader { + List of(Class targetClass); + } + + private static class DefaultMappingModelReader implements MappingModelReader, ListMappingModelReader { + private ObjectMapper objectMapper; + private Object model; + + private DefaultMappingModelReader(Object model) { + this.model = model; + this.objectMapper = JsonModel.getObjectMapper(); + } + + @Override + public ListMappingModelReader toList() { + return this; + } + + @Override + public List of(Class targetClass) { + return toListOf(targetClass); + } + + @Override + public List toListOf(Class targetClass) { + if (!(model instanceof List)) { + model = asList(model); + } + CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(List.class, targetClass); + return objectMapper.convertValue(model, colType); + } + + @Override + public Set toSetOf(Class targetClass) { + if (!(model instanceof List)) { + Set setModel = new HashSet(); + setModel.add(model); + model = setModel; + } + CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(Set.class, targetClass); + return objectMapper.convertValue(model, colType); + } + + @Override + public T to(Class targetClass) { + return objectMapper.convertValue(model, targetClass); + } + + + } + // -------------------------------------------------------- // // Private helpers diff --git a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java index 494ac769..64baaad7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -27,6 +27,10 @@ import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import static org.apache.commons.lang.Validate.isTrue; +import static org.apache.commons.lang.Validate.notEmpty; +import static org.apache.commons.lang.Validate.notNull; + /** *

* JsonPath is to JSON what XPATH is to XML, a simple way to extract parts of a given document. JsonPath is @@ -138,17 +142,19 @@ public class JsonPath { * Applies this JsonPath to the provided json document. * Note that the document must either a {@link List} or a {@link Map} * - * @param json a container Object ({@link List} or {@link Map}) + * @param jsonObject a container Object ({@link List} or {@link Map}) * @param expected return type * @return list of objects matched by the given path */ @SuppressWarnings({"unchecked"}) - public T read(Object json) { - if (!(json instanceof Map) && !(json instanceof List)) { + public T read(Object jsonObject) { + notNull(jsonObject, "json can not be null"); + + if (!(jsonObject instanceof Map) && !(jsonObject instanceof List)) { throw new IllegalArgumentException("Invalid container object"); } - Object result = json; + Object result = jsonObject; boolean inArrayContext = false; @@ -172,6 +178,8 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public T read(String json) { + notEmpty(json, "json can not be null or empty"); + return (T) read(JsonProviderFactory.getInstance().parse(json)); } @@ -185,6 +193,8 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public T read(URL jsonURL) throws IOException { + notNull(jsonURL, "json URL can not be null"); + BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(jsonURL.openStream())); @@ -204,6 +214,9 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public T read(File jsonFile) throws IOException { + notNull(jsonFile, "json file can not be null"); + isTrue(jsonFile.exists(), "json file does not exist"); + FileInputStream fis = null; try { fis = new FileInputStream(jsonFile); @@ -223,6 +236,8 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public T read(InputStream jsonInputStream) throws IOException { + notNull(jsonInputStream, "json input stream can not be null"); + try { return (T) read(JsonProviderFactory.getInstance().parse(jsonInputStream)); } finally { @@ -243,6 +258,8 @@ public class JsonPath { * @return compiled JsonPath */ public static JsonPath compile(String jsonPath) { + notEmpty(jsonPath, "json can not be null or empty"); + return new JsonPath(jsonPath); } @@ -263,6 +280,9 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public static T read(String json, String jsonPath) { + notEmpty(json, "json can not be null or empty"); + notEmpty(jsonPath, "jsonPath can not be null or empty"); + return (T) compile(jsonPath).read(json); } @@ -276,6 +296,9 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public static T read(Object json, String jsonPath) { + notNull(json, "json can not be null"); + notNull(jsonPath, "jsonPath can not be null"); + return (T) compile(jsonPath).read(json); } @@ -289,6 +312,9 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public static T read(URL jsonURL, String jsonPath) throws IOException { + notNull(jsonURL, "json URL can not be null"); + notEmpty(jsonPath, "jsonPath can not be null or empty"); + return (T) compile(jsonPath).read(jsonURL); } @@ -302,6 +328,9 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public static T read(File jsonFile, String jsonPath) throws IOException { + notNull(jsonFile, "json file can not be null"); + notEmpty(jsonPath, "jsonPath can not be null or empty"); + return (T) compile(jsonPath).read(jsonFile); } @@ -315,6 +344,9 @@ public class JsonPath { */ @SuppressWarnings({"unchecked"}) public static T read(InputStream jsonInputStream, String jsonPath) throws IOException { + notNull(jsonInputStream, "json input stream can not be null"); + notEmpty(jsonPath, "jsonPath can not be null or empty"); + return (T) compile(jsonPath).read(jsonInputStream); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java index b3d674aa..b936c9fa 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java @@ -18,6 +18,7 @@ import com.jayway.jsonpath.InvalidJsonException; import java.io.InputStream; import java.io.Reader; +import java.net.URL; import java.util.List; import java.util.Map; @@ -34,6 +35,7 @@ public interface JsonProvider { Object parse(InputStream jsonStream) throws InvalidJsonException; + String toJson(Object obj); Map createMap(); diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JsonSmartProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JsonSmartProvider.java index 23e351a7..12df3199 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JsonSmartProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JsonSmartProvider.java @@ -84,12 +84,14 @@ public class JsonSmartProvider extends AbstractJsonProvider { @Override public String toJson(Object obj) { - if(!(obj instanceof JSONAware)){ - throw new InvalidJsonException(); - } - JSONAware aware = (JSONAware)obj; - return aware.toJSONString(); + if(obj instanceof Map) { + return JSONObject.toJSONString((Map) obj); + } else if(obj instanceof List){ + return JSONArray.toJSONString((List) obj); + } else { + throw new UnsupportedOperationException(obj.getClass().getName() + " can not be converted to JSON"); + } } public Mode getMode() { diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonModelMapperTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonModelMapperTest.java deleted file mode 100644 index 699eb5b1..00000000 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonModelMapperTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.jayway.jsonpath; - -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; -import org.junit.Test; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertEquals; - -/** - * Created by IntelliJ IDEA. - * User: kallestenflo - * Date: 3/2/12 - * Time: 10:50 AM - */ -public class JsonModelMapperTest { - - public final static String DOCUMENT = - "{ \"store\": {\n" + - " \"book\": [ \n" + - " { \"category\": \"reference\",\n" + - " \"author\": \"Nigel Rees\",\n" + - " \"title\": \"Sayings of the Century\",\n" + - " \"displayPrice\": 8.95\n" + - " },\n" + - " { \"category\": \"fiction\",\n" + - " \"author\": \"Evelyn Waugh\",\n" + - " \"title\": \"Sword of Honour\",\n" + - " \"displayPrice\": 12.99\n" + - " },\n" + - " { \"category\": \"fiction\",\n" + - " \"author\": \"Herman Melville\",\n" + - " \"title\": \"Moby Dick\",\n" + - " \"isbn\": \"0-553-21311-3\",\n" + - " \"displayPrice\": 8.99\n" + - " },\n" + - " { \"category\": \"fiction\",\n" + - " \"author\": \"J. R. R. Tolkien\",\n" + - " \"title\": \"The Lord of the Rings\",\n" + - " \"isbn\": \"0-395-19395-8\",\n" + - " \"displayPrice\": 22.99\n" + - " }\n" + - " ],\n" + - " \"bicycle\": {\n" + - " \"color\": \"red\",\n" + - " \"displayPrice\": 19.95,\n" + - " \"foo:bar\": \"fooBar\",\n" + - " \"dot.notation\": \"new\",\n" + - " \"dash-notation\": \"dashes\"\n" + - " }\n" + - " }\n" + - "}"; - - - @Test - public void map_a_json_model() throws Exception { - - JsonModel model = JsonModel.create(DOCUMENT); - - List booksList = model.map("$.store.book[0,1]").toListOf(Book.class); - - Set bookSet = model.map("$.store.book[0,1]").toSetOf(Book.class); - - Book book = model.map("$.store.book[1]").to(Book.class); - - System.out.println("test"); - - } - - @Test - public void a_book_can_be_mapped() throws Exception { - - - //JsonPath.convert(DOCUMENT, "$.store.book[0,1]", List.class).to() - - //List books = JsonPath.read(DOCUMENT, "$.store.book[0,1]", List.class); - - ObjectMapper objectMapper = new ObjectMapper(); - - - /* - - -//Standard -List res = JsonPath.read(DOCUMENT, "$.store.book[0,1]"); -//or -List res = JsonPath.read(DOCUMENT, "$.store.book[0,1]", List.class); - - -//POJO Mapping med jackson ObjectMapper -List res = JsonPath.asList().of(Book.class).read(DOCUMENT, "$.store.book[0,1]"); - -List res = JsonPath.asListOf(Book.class).read(DOCUMENT, "$.store.book[0,1]"); - -Book res = JsonPath.as(Book.class).read(DOCUMENT, "$.store.book[0]"); - - */ - } - - public static class Book { - private String category; - private String author; - private String title; - private Double displayPrice; - - public Book() { - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public Double getDisplayPrice() { - return displayPrice; - } - - public void setDisplayPrice(Double displayPrice) { - this.displayPrice = displayPrice; - } - } - -} - - - diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java index c493dda2..c160b779 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java @@ -1,10 +1,14 @@ package com.jayway.jsonpath; -import com.jayway.jsonpath.spi.JsonProvider; -import com.jayway.jsonpath.spi.JsonProviderFactory; -import com.jayway.jsonpath.util.ScriptEngineJsonPath; import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.net.URL; +import java.util.*; + +import static java.util.Arrays.asList; +import static junit.framework.Assert.assertEquals; + /** * Created by IntelliJ IDEA. * User: kallestenflo @@ -48,64 +52,71 @@ public class JsonModelTest { " }\n" + "}"; - public final static JsonModel MODEL = new JsonModel(DOCUMENT); @Test - public void a_path_can_be_read() throws Exception { - - JsonPath path = JsonPath.compile("$.store.book[*].title"); - - JsonModel model = new JsonModel(DOCUMENT); - - Object result = model.get(path); - - System.out.println(JsonProviderFactory.getInstance().toJson(result)); - + public void a_json_document_can_be_fetched_with_a_URL() throws Exception { + URL url = new URL("http://maps.googleapis.com/maps/api/geocode/json"); + assertEquals("REQUEST_DENIED", JsonModel.create(url).get("status")); + } + @Test + public void a_json_document_can_be_fetched_with_a_InputStream() throws Exception { + ByteArrayInputStream bis = new ByteArrayInputStream(DOCUMENT.getBytes()); + assertEquals("Nigel Rees", JsonModel.create(bis).get("store.book[0].author")); } @Test - public void test2() throws Exception { - JsonPath path = JsonPath.compile("$.."); + public void test_a_sub_model_can_be_fetched_and_read() throws Exception { + JsonModel model = JsonModel.create(DOCUMENT); + assertEquals("Nigel Rees", model.getSubModel("$store.book[0]").get("author")); + assertEquals("Nigel Rees", model.getSubModel(JsonPath.compile("$store.book[0]")).get("author")); + } - System.out.println(ScriptEngineJsonPath.eval(DOCUMENT, path.getPath())); + @Test + public void maps_and_list_can_queried() throws Exception { + Map doc = new HashMap(); + doc.put("items", asList(0, 1, 2)); + doc.put("child", Collections.singletonMap("key", "value")); - JsonModel model = new JsonModel(DOCUMENT); + JsonModel model = JsonModel.create(doc); - System.out.println(model.getJson(path)); + assertEquals("value", model.get("$child.key")); + assertEquals(1, model.get("$items[1]")); + assertEquals("{\"child\":{\"key\":\"value\"},\"items\":[0,1,2]}", model.toJson()); } - @Test - public void test3() throws Exception { - JsonPath path = JsonPath.compile("$..[0]"); + public void map_a_json_model() throws Exception { - //System.out.println(ScriptEngineJsonPath.eval(DOCUMENT, path.getPath())); + JsonModel model = JsonModel.create(DOCUMENT); - System.out.println(MODEL.getJson(path)); - } + List booksList = model.map("$.store.book[0,1]").toListOf(Book.class); - @Test - public void test4() throws Exception { - JsonPath path = JsonPath.compile("$..*"); + Set bookSet = model.map("$.store.book[0,1]").toSetOf(Book.class); - System.out.println(ScriptEngineJsonPath.eval(DOCUMENT, path.getPath())); - System.out.println("--------------------------------"); + Book book = model.map("$.store.book[1]").to(Book.class); - JsonModel model = new JsonModel(DOCUMENT); - System.out.println(model.getJson(path)); - } + assertEquals("fiction", book.category); + assertEquals("Evelyn Waugh", book.author); + assertEquals("Sword of Honour", book.title); + assertEquals(12.99D, book.price); - @Test - public void test5() throws Exception { + List booksList2 = model.map("$.store.book[*]").toListOf(Book.class); - JsonModel model = new JsonModel(DOCUMENT); + List booksList3 = model.map("$.store.book[*]").toList().of(Book.class); - JsonModel model2 = model.getSubModel("store.book[0]"); + System.out.println("asd"); + } - System.out.println(model2.getJson()); + public static class Book { + public String category; + public String author; + public String title; + public String isbn; + public Double price; } + }