Browse Source

Improved JsonModel map() functionality.

pull/7/head
Kalle Stenflo 12 years ago
parent
commit
6ed6de77e8
  1. 26
      json-path/src/main/java/com/jayway/jsonpath/InvalidModelPathException.java
  2. 204
      json-path/src/main/java/com/jayway/jsonpath/JsonModel.java
  3. 40
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  4. 2
      json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java
  5. 12
      json-path/src/main/java/com/jayway/jsonpath/spi/impl/JsonSmartProvider.java
  6. 148
      json-path/src/test/java/com/jayway/jsonpath/JsonModelMapperTest.java
  7. 87
      json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java

26
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);
}
}

204
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> 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> 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 <T> List<T> toListOf(Class<T> targetClass) {
Object model = JsonModel.this.get(jsonPath);
CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(List.class, targetClass);
return objectMapper.convertValue(model, colType);
}
@Override
public <T> Set<T> toSetOf(Class<T> targetClass) {
Object model = JsonModel.this.get(jsonPath);
CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(Set.class, targetClass);
return objectMapper.convertValue(model, colType);
}
@Override
public <T> T to(Class<T> 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();
<T> List<T> toListOf(Class<T> targetClass);
<T> Set<T> toSetOf(Class<T> targetClass);
@ -134,6 +233,57 @@ public class JsonModel {
<T> T to(Class<T> targetClass);
}
public interface ListMappingModelReader {
<T> List<T> of(Class<T> 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 <T> List<T> of(Class<T> targetClass) {
return toListOf(targetClass);
}
@Override
public <T> List<T> toListOf(Class<T> targetClass) {
if (!(model instanceof List)) {
model = asList(model);
}
CollectionType colType = objectMapper.getTypeFactory().constructCollectionType(List.class, targetClass);
return objectMapper.convertValue(model, colType);
}
@Override
public <T> Set<T> toSetOf(Class<T> 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> T to(Class<T> targetClass) {
return objectMapper.convertValue(model, targetClass);
}
}
// --------------------------------------------------------
//
// Private helpers

40
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;
/**
* <p/>
* 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 <T> expected return type
* @return list of objects matched by the given path
*/
@SuppressWarnings({"unchecked"})
public <T> T read(Object json) {
if (!(json instanceof Map) && !(json instanceof List)) {
public <T> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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);
}

2
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<String, Object> createMap();

12
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<String, ? extends Object>) obj);
} else if(obj instanceof List){
return JSONArray.toJSONString((List<? extends Object>) obj);
} else {
throw new UnsupportedOperationException(obj.getClass().getName() + " can not be converted to JSON");
}
}
public Mode getMode() {

148
json-path/src/test/java/com/jayway/jsonpath/JsonModelMapperTest.java

@ -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<Book> booksList = model.map("$.store.book[0,1]").toListOf(Book.class);
Set<Book> 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<Map> res = JsonPath.read(DOCUMENT, "$.store.book[0,1]");
//or
List<Map> res = JsonPath.read(DOCUMENT, "$.store.book[0,1]", List.class);
//POJO Mapping med jackson ObjectMapper
List<Book> res = JsonPath.asList().of(Book.class).read(DOCUMENT, "$.store.book[0,1]");
List<Book> 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;
}
}
}

87
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<String, Object> doc = new HashMap<String, Object>();
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<Book> booksList = model.map("$.store.book[0,1]").toListOf(Book.class);
@Test
public void test4() throws Exception {
JsonPath path = JsonPath.compile("$..*");
Set<Book> 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<Book> booksList2 = model.map("$.store.book[*]").toListOf(Book.class);
JsonModel model = new JsonModel(DOCUMENT);
List<Book> 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;
}
}

Loading…
Cancel
Save