From 7621cd1d43da9d04e784c277d42c81336d4f9e72 Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Wed, 17 Sep 2014 11:10:01 +0200 Subject: [PATCH] Gson support (work in progress). --- .../java/com/jayway/jsonpath/Criteria.java | 6 +- .../jsonpath/internal/PathCompiler.java | 2 +- .../compiler/EvaluationContextImpl.java | 4 +- .../jsonpath/internal/compiler/PathToken.java | 4 +- .../internal/compiler/ScanPathToken.java | 4 +- .../converter/DefaultConversionProvider.java | 14 +++ .../internal/spi/converter/GsonConverter.java | 90 +++++++++++++++++++ .../spi/converter/StringConverter.java | 1 - .../spi/json/AbstractJsonProvider.java | 24 ++++- .../internal/spi/json/GsonProvider.java | 48 ++++++++-- .../jsonpath/spi/json/JsonProvider.java | 15 +++- .../java/com/jayway/jsonpath/BaseTest.java | 3 +- .../com/jayway/jsonpath/ConverterTest.java | 7 ++ .../java/com/jayway/jsonpath/FilterTest.java | 19 ---- .../java/com/jayway/jsonpath/OptionsTest.java | 12 +-- .../com/jayway/jsonpath/ReturnTypeTest.java | 2 +- .../com/jayway/jsonpath/old/JsonPathTest.java | 5 +- 17 files changed, 206 insertions(+), 54 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/GsonConverter.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java index bab6e9b7..eb28f8b1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -148,10 +148,10 @@ public class Criteria implements Predicate { boolean res; if (configuration.jsonProvider().isArray(actual)) { int length = configuration.jsonProvider().length(actual); - res = length == size; + res = (length == size); logger.debug("Array with size {} {} {} => {}", length, name(), size, res); - } else if (actual instanceof String) { - int length = ((String) actual).length(); + } else if (configuration.jsonProvider().isString(actual)) { + int length = configuration.jsonProvider().length(actual); res = length == size; logger.debug("String with length {} {} {} => {}", length, name(), size, res); } else { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java index 8f35a65c..4c7f032a 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java @@ -5,8 +5,8 @@ import com.jayway.jsonpath.Filter; import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.compiler.ArrayPathToken; -import com.jayway.jsonpath.internal.compiler.PredicatePathToken; import com.jayway.jsonpath.internal.compiler.PathToken; +import com.jayway.jsonpath.internal.compiler.PredicatePathToken; import com.jayway.jsonpath.internal.compiler.PropertyPathToken; import com.jayway.jsonpath.internal.compiler.RootPathToken; import com.jayway.jsonpath.internal.compiler.ScanPathToken; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java index 50f4bbbf..0a4131c7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java @@ -60,9 +60,9 @@ public class EvaluationContextImpl implements EvaluationContext { if(resultIndex == 0){ throw new PathNotFoundException("No results for path: " + path.toString()); } - return (T) jsonProvider().unwrap(jsonProvider().getArrayIndex(valueResult, 0)); + return (T) jsonProvider().getArrayIndex(valueResult, 0); } - return (T) jsonProvider().unwrap(valueResult); + return (T)valueResult; } @SuppressWarnings("unchecked") diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PathToken.java index 2c285a85..77efaf1a 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PathToken.java @@ -27,7 +27,7 @@ public abstract class PathToken { if(propertyVal == JsonProvider.UNDEFINED){ if(isLeaf()) { if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ - propertyVal = null; + propertyVal = ctx.jsonProvider().createNull(); } else { if(ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)){ return; @@ -58,7 +58,7 @@ public abstract class PathToken { Object propertyVal = readObjectProperty(property, model, ctx); if(propertyVal == JsonProvider.UNDEFINED){ if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ - propertyVal = null; + propertyVal = ctx.jsonProvider().createNull();; } else { continue; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java index f7270789..b785456e 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java @@ -1,11 +1,11 @@ package com.jayway.jsonpath.internal.compiler; +import com.jayway.jsonpath.spi.json.JsonProvider; + import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -import com.jayway.jsonpath.spi.json.JsonProvider; - /** * */ diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/DefaultConversionProvider.java b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/DefaultConversionProvider.java index 8f6e3e6e..bc275681 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/DefaultConversionProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/DefaultConversionProvider.java @@ -3,19 +3,33 @@ package com.jayway.jsonpath.internal.spi.converter; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.spi.converter.ConversionProvider; import com.jayway.jsonpath.spi.converter.Converter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; public class DefaultConversionProvider implements ConversionProvider { + private static final Logger logger = LoggerFactory.getLogger(DefaultConversionProvider.class); + private HashMap, HashMap, Converter>> converters = new HashMap, HashMap, Converter>>(); public DefaultConversionProvider(){ addConverters(new NumberConverter()); addConverters(new StringConverter()); addConverters(new DateConverter()); + + try { + Class.forName("com.google.gson.Gson"); + addConverters(new GsonConverter()); + } catch (ClassNotFoundException e) { + logger.debug("Gson not found on class path. No converters configured."); + } } + + + public void addConverters(ConverterBase converter) { for (Converter.ConvertiblePair convertible : converter.getConvertibleTypes()) { if(!converters.containsKey(convertible.getTargetType())){ diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/GsonConverter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/GsonConverter.java new file mode 100644 index 00000000..4be46efa --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/GsonConverter.java @@ -0,0 +1,90 @@ +package com.jayway.jsonpath.internal.spi.converter; + +import com.google.gson.JsonArray; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.spi.converter.ConversionException; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class GsonConverter extends ConverterBase { + + public GsonConverter() { + register(JsonPrimitive.class, Integer.class); + register(JsonPrimitive.class, Long.class); + register(JsonPrimitive.class, Float.class); + register(JsonPrimitive.class, Double.class); + register(JsonPrimitive.class, BigDecimal.class); + register(JsonPrimitive.class, BigInteger.class); + register(JsonPrimitive.class, Date.class); + + register(JsonPrimitive.class, String.class); + + register(JsonPrimitive.class, Boolean.class); + + register(JsonArray.class, List.class); + register(JsonArray.class, Map.class); + + + } + + @Override + public Object convert(Object src, Class srcType, Class targetType, Configuration conf) { + + assertValidConversion(src, srcType, targetType); + + if (src == null || src.getClass().equals(JsonNull.class)) { + return null; + } + + if (JsonPrimitive.class.isAssignableFrom(srcType)) { + + JsonPrimitive primitive = (JsonPrimitive) src; + if (targetType.equals(Long.class)) { + return primitive.getAsLong(); + } else if (targetType.equals(Integer.class)) { + return primitive.getAsInt(); + } else if (targetType.equals(BigInteger.class)) { + return primitive.getAsBigInteger(); + } else if (targetType.equals(Byte.class)) { + return primitive.getAsByte(); + } else if (targetType.equals(BigDecimal.class)) { + return primitive.getAsBigDecimal(); + } else if (targetType.equals(Double.class)) { + return primitive.getAsDouble(); + } else if (targetType.equals(Float.class)) { + return primitive.getAsFloat(); + } else if (targetType.equals(String.class)) { + return primitive.getAsString(); + } else if (targetType.equals(Boolean.class)) { + return primitive.getAsBoolean(); + } else if (targetType.equals(Date.class)) { + if(primitive.isNumber()){ + return new Date(primitive.getAsLong()); + } else if(primitive.isString()){ + try { + return DateFormat.getInstance().parse(primitive.getAsString()); + } catch (ParseException e) { + throw new ConversionException(e); + } + } + } + + + } else if (JsonObject.class.isAssignableFrom(srcType)) { + + } else if (JsonArray.class.isAssignableFrom(srcType)) { + + } + + return null; + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/StringConverter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/StringConverter.java index 2d40be9d..c9f60824 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/StringConverter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/converter/StringConverter.java @@ -1,7 +1,6 @@ package com.jayway.jsonpath.internal.spi.converter; import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.spi.converter.Converter; public class StringConverter extends ConverterBase { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/AbstractJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/AbstractJsonProvider.java index 71e8f2e5..7204c6e2 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/AbstractJsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/AbstractJsonProvider.java @@ -34,11 +34,15 @@ public abstract class AbstractJsonProvider implements JsonProvider { } public int compare(Object expected, Object providerParsed) throws ValueCompareException { - if (isNullish(expected) && !isNullish(providerParsed)) { + + boolean expNullish = isNullish(expected); + boolean provNullish = isNullish(providerParsed); + + if (expNullish && !provNullish) { return -1; - } else if (!isNullish(expected) && isNullish(providerParsed)) { + } else if (!expNullish && provNullish) { return 1; - } else if (isNullish(expected) && isNullish(providerParsed)) { + } else if (expNullish && provNullish) { return 0; } else if (expected instanceof String && providerParsed instanceof String) { return ((String) expected).compareTo((String) providerParsed); @@ -64,6 +68,10 @@ public abstract class AbstractJsonProvider implements JsonProvider { return (o == null || ((o instanceof String) && ("null".equals(o)))); } + @Override + public Object createNull(){ + return null; + } /** * checks if object is an array @@ -75,6 +83,10 @@ public abstract class AbstractJsonProvider implements JsonProvider { return (obj instanceof List); } + public boolean isString(Object obj){ + return (obj instanceof String); + } + /** * Extracts a value from an array @@ -166,8 +178,12 @@ public abstract class AbstractJsonProvider implements JsonProvider { public int length(Object obj) { if (isArray(obj)) { return ((List) obj).size(); + } else if (isMap(obj)){ + return getPropertyKeys(obj).size(); + } else if(obj instanceof String){ + return ((String)obj).length(); } - return getPropertyKeys(obj).size(); + throw new RuntimeException("length operation can not applied to " + obj!=null?obj.getClass().getName():"null"); } /** diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/GsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/GsonProvider.java index e1d89d83..a820a2eb 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/GsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/json/GsonProvider.java @@ -7,6 +7,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; +import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; @@ -46,11 +47,13 @@ public class GsonProvider extends AbstractJsonProvider { JsonElement element = (JsonElement) providerParsed; - if (isNullish(expected) && !element.isJsonNull()) { + boolean nullish = isNullish(expected); + + if (nullish && !element.isJsonNull()) { return -1; - } else if (!isNullish(expected) && element.isJsonNull()) { + } else if (!nullish && element.isJsonNull()) { return 1; - } else if (isNullish(expected) && element.isJsonNull()) { + } else if (nullish && element.isJsonNull()) { return 0; } if(element.isJsonPrimitive()){ @@ -175,6 +178,12 @@ public class GsonProvider extends AbstractJsonProvider { return obj.toString(); } + + @Override + public Object createNull(){ + return JsonNull.INSTANCE; + } + @Override public Object createMap() { return new JsonObject(); @@ -190,6 +199,17 @@ public class GsonProvider extends AbstractJsonProvider { return (obj instanceof JsonArray); } + public boolean isString(Object obj){ + if(obj == null) { + return false; + } + JsonElement element = toJsonElement(obj); + if(element.isJsonPrimitive()){ + return element.getAsJsonPrimitive().isString(); + } + return false; + } + @Override public Object getArrayIndex(Object obj, int idx) { return toJsonArray(obj).get(idx); @@ -209,7 +229,7 @@ public class GsonProvider extends AbstractJsonProvider { @Override public void setProperty(Object obj, Object key, Object value) { if (isMap(obj)) - toJsonObject(obj).add(key.toString(), toJsonElement(value)); + toJsonObject(obj).add(key.toString(), createJsonElement(value)); else { JsonArray array = toJsonArray(obj); int index; @@ -219,9 +239,9 @@ public class GsonProvider extends AbstractJsonProvider { index = array.size(); } if (index == array.size()) { - array.add(toJsonElement(value)); + array.add(createJsonElement(value)); } else { - array.set(index, toJsonElement(value)); + array.set(index, createJsonElement(value)); } } } @@ -244,9 +264,17 @@ public class GsonProvider extends AbstractJsonProvider { public int length(Object obj) { if (isArray(obj)) { return toJsonArray(obj).size(); - } else { + } else if(isMap(obj)){ return toJsonObject(obj).entrySet().size(); + } else { + if(obj instanceof JsonElement){ + JsonElement element = toJsonElement(obj); + if(element.isJsonPrimitive()){ + return element.toString().length(); + } + } } + throw new RuntimeException("length operation can not applied to " + obj!=null?obj.getClass().getName():"null"); } @Override @@ -263,7 +291,7 @@ public class GsonProvider extends AbstractJsonProvider { } } - private JsonElement toJsonElement(Object o) { + private JsonElement createJsonElement(Object o) { return gson.toJsonTree(o); } @@ -275,6 +303,10 @@ public class GsonProvider extends AbstractJsonProvider { return (JsonObject) o; } + private JsonElement toJsonElement(Object o) { + return (JsonElement) o; + } + public static class NumberTypeAdapter implements JsonSerializer, JsonDeserializer, diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java index 2f28142e..bf65e05d 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonProvider.java @@ -37,6 +37,8 @@ public interface JsonProvider { String toJson(Object obj); + Object createNull(); + Object createMap(); Object createArray(); @@ -50,9 +52,18 @@ public interface JsonProvider { boolean isArray(Object obj); /** - * Get the length of an array or object + * checks if object is a string * - * @param obj an array or an object + * @param obj object to check + * @return true if obj is an array + */ + boolean isString(Object obj); + + + /** + * Get the length of an json array, json object or a json string + * + * @param obj an array or object or a string * @return the number of entries in the array or object */ int length(Object obj); diff --git a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java index 6156f892..4a925035 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java @@ -27,7 +27,8 @@ public class BaseTest { return new DefaultConversionProvider(); } }); - } */ + } + */ diff --git a/json-path/src/test/java/com/jayway/jsonpath/ConverterTest.java b/json-path/src/test/java/com/jayway/jsonpath/ConverterTest.java index c2c94288..6be41fb3 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/ConverterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/ConverterTest.java @@ -1,9 +1,15 @@ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.spi.converter.DefaultConversionProvider; +import com.jayway.jsonpath.internal.spi.json.GsonProvider; +import com.jayway.jsonpath.spi.converter.ConversionProvider; +import com.jayway.jsonpath.spi.json.JsonProvider; import org.junit.Test; import java.math.BigDecimal; import java.util.Date; +import java.util.EnumSet; +import java.util.Set; import static com.jayway.jsonpath.JsonPath.parse; import static java.util.Collections.singletonMap; @@ -11,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class ConverterTest extends BaseTest { + @Test public void an_Integer_can_be_converted_to_a_Long() { assertThat(parse("{\"val\": 1}").read("val", Long.class)).isEqualTo(1L); diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java index 511413b4..5ced853d 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java @@ -12,26 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class FilterTest extends BaseTest { - /* - static { - Configuration.setDefaults(new Configuration.Defaults() { - @Override - public JsonProvider jsonProvider() { - return new GsonProvider(); - } - - @Override - public Set