diff --git a/build.gradle b/build.gradle
index 1bc6a8fd..141de6f6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,6 +5,7 @@ buildscript {
dependencies {
classpath 'me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1'
classpath 'com.github.jengelman.gradle.plugins:shadow:0.8'
+ classpath 'me.champeau.gradle:japicmp-gradle-plugin:0.1.0'
}
}
@@ -73,4 +74,5 @@ task wrapper(type: Wrapper) {
}
//Task used by Heroku for staging
-task stage(dependsOn: [':json-path-web-test:clean', 'json-path-web-test:shadowJar']) {}
\ No newline at end of file
+task stage(dependsOn: [':json-path-web-test:clean', 'json-path-web-test:shadowJar']) {}
+apply from: "$rootDir/gradle/binaryCompatibility.gradle"
\ No newline at end of file
diff --git a/changelog.md b/changelog.md
index 0e881069..e55688bd 100644
--- a/changelog.md
+++ b/changelog.md
@@ -19,6 +19,7 @@ In The Pipe
* Negate exist checks in inline filters (not defined or null)
`parse(JSON_DOCUMENT).read("$.store.book[?(!@.isbn)]")`
* Improved object mapping with Jackson and Gson (now handles generic types)
+* JacksonTreeJsonProvider supporting path operations on `com.fasterxml.jackson.databind.JsonNode`
* Exceptions thrown by JsonPath.compile are now wrapped in an InvalidPathException
* Fixed Deep scanning issue (#60)
diff --git a/gradle/binaryCompatibility.gradle b/gradle/binaryCompatibility.gradle
new file mode 100644
index 00000000..5f9de439
--- /dev/null
+++ b/gradle/binaryCompatibility.gradle
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2003-2014 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.
+ */
+
+import groovy.text.markup.MarkupTemplateEngine
+import groovy.text.markup.TemplateConfiguration
+
+
+buildscript {
+ // this block should not be necessary, but for some reason it fails without!
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'me.champeau.gradle:japicmp-gradle-plugin:0.1.0'
+ }
+}
+
+
+task checkBinaryCompatibility {
+ description = 'Generates binary compatibility reports'
+}
+
+
+if (JavaVersion.current().isJava7Compatible()) {
+ apply plugin: 'me.champeau.gradle.japicmp'
+
+ def referenceMinorVersion = '1.1.0'
+
+ def reportGenerator = { model ->
+ outputProcessor {
+
+ def skipClass = { c -> c.fullyQualifiedName.startsWith('com.jayway.jsonpath.internal') || c.fullyQualifiedName.contains('$')}
+ def skipMethod = { c, m -> skipClass(c) || m.name =~ /access\$[0-9]+/ }
+ def violations = [:].withDefault {
+ // key = class name
+ // value = map of violations
+ [:].withDefault { [] }
+ }
+ removedMethod { c, m ->
+ if (!skipMethod(c, m)) {
+ def level = m.name.startsWith('super$') ? 'warning' : 'error'
+ violations[c.fullyQualifiedName][level] << "Method ${m.name} has been removed"
+ }
+ }
+ removedClass { c ->
+ if (!skipClass(c)) {
+ violations[c.fullyQualifiedName].error << "Class has been removed"
+ }
+ }
+
+ modifiedMethod { c, m ->
+ if (!skipMethod(c, m)) {
+ violations[c.fullyQualifiedName].warning << """
Method ${m.name} has been modified
+From
${m.oldMethod.get()?.longName}
to ${m.newMethod.get()?.longName}
"""
+ }
+ }
+
+ newClass { c ->
+ if (!skipClass(c)) {
+ violations[c.fullyQualifiedName].info << "Class has been added"
+ }
+ }
+ newMethod { c, m ->
+ if (!skipMethod(c, m)) {
+ violations[c.fullyQualifiedName].info << """Method ${m.name} has been added
+Signature:
${m.newMethod.get()?.longName}
"""
+ }
+ }
+ after {
+ model.violations = violations
+ }
+ }
+ }
+
+// using a global engine for all tasks in order to increase performance
+ def configDir = file("$rootDir/gradle")
+ def templateFile = 'binarycompat-report.groovy'
+ def templateConfiguration = new TemplateConfiguration()
+ templateConfiguration.with {
+ autoIndent = true
+ autoNewLine = true
+ }
+ def engine = new MarkupTemplateEngine(this.class.classLoader, configDir, templateConfiguration)
+
+
+ subprojects {
+
+ task japicmp(type: me.champeau.gradle.ArtifactJapicmpTask) {
+ dependsOn jar
+ //baseline = "com.jayway.jsonpath:${project.name}:${referenceMinorVersion}@jar"
+ baseline = "com.jayway.jsonpath:${project.name}:+@jar"
+ to = jar.archivePath
+ accessModifier = 'protected'
+ onlyModified = true
+ failOnModification = false
+ txtOutputFile = file("$buildDir/reports/japi.txt")
+
+ def htmlReportFile = file("${buildDir}/reports/binary-compat-${project.name}.html")
+ inputs.file file("$configDir/$templateFile")
+ inputs.file templateFile
+ outputs.file htmlReportFile
+
+ def model = [title : "Binary compatibility report for ${project.name}",
+ project : project,
+ baseline: baseline,
+ archive : to.name]
+ outputProcessor(reportGenerator.curry(model))
+
+ doLast {
+ htmlReportFile.withWriter('utf-8') { wrt ->
+ engine.createTemplateByPath(templateFile).make(model).writeTo(wrt)
+ }
+ }
+
+ }
+ }
+
+ subprojects {
+ check.dependsOn(checkBinaryCompatibility)
+ tasks.withType(me.champeau.gradle.ArtifactJapicmpTask) { task ->
+ checkBinaryCompatibility.dependsOn(task)
+ }
+ }
+}
\ No newline at end of file
diff --git a/gradle/binarycompat-report.groovy b/gradle/binarycompat-report.groovy
new file mode 100644
index 00000000..9e3e6633
--- /dev/null
+++ b/gradle/binarycompat-report.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2003-2014 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.
+ */
+
+/**
+ * A template which generates an HTML report from the bincompat XML report
+ */
+modelTypes = {
+ String title
+ String baseline
+ String archive
+ Map>> violations
+}
+
+def severityMapping = [
+ error : 'danger',
+ warning: 'warning',
+ info : 'info',
+ ignore : 'success'
+]
+
+yieldUnescaped ''
+
+
+html {
+ head {
+ meta 'charset': "utf-8"
+ meta 'http-equiv': "content-type", content: "text/html; charset=utf-8"
+ meta 'http-equiv': "X-UA-Compatible", content: "IE=edge"
+ meta name: "viewport", content: "width=device-width, initial-scale=1"
+
+ title(title)
+ link href: "http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css", rel: "stylesheet"
+ link href: "http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css", rel: "stylesheet"
+ link href: "http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css", rel: "stylesheet"
+ }
+
+ body {
+ div(class:'navbar navbar-inverse navbar-fixed-top', role:'navigation') {
+ div(class:'container') {
+ div(class:'navbar-header') {
+ button(type:'button', class:'navbar-toggle', 'data-toggle':'collapse', 'data-target':'navbar-collaspe') {
+ span(class:'sr-only', 'Toggle navigation')
+ span(class:'icon-bar'){}
+ span(class:'icon-bar'){}
+ span(class:'icon-bar'){}
+ }
+ a(class:'navbar-brand',href:'#', 'Binary compatibility report')
+ }
+ div(class:'navbar-collapse collapse') {
+ ul(class:"nav navbar-nav") {
+ li(class: 'dropdown') {
+ a(id: 'severityDropdown', href: '#', class: 'dropdown-toggle', 'data-toggle': 'dropdown', 'Severity ')
+ ul(class: "dropdown-menu dropdown-severity", role: "menu") {
+ li(role: 'presentation', class: 'active') {
+ a(role: 'menuitem', tabindex: '-1', href: '#', 'All levels')
+ }
+ li(role: 'presentation') { a(role: 'menuitem', tabindex: '-1', href: '#', 'Error') }
+ li(role: 'presentation') { a(role: 'menuitem', tabindex: '-1', href: '#', 'Warning') }
+ li(role: 'presentation') { a(role: 'menuitem', tabindex: '-1', href: '#', 'Info') }
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+
+ div(class: 'container') {
+ div(class:'jumbotron') {
+ div(class:'container') {
+ div(class: 'page-header') {
+ h1 'Binary compatibility'
+ p "Comparing ${archive} to reference ${baseline}"
+ p {
+ yield "Be warned that this report is not perfect and depends on what "
+ a(href: 'https://github.com/siom79/japicmp', 'JApicmp')
+ yield " is capable to detect."
+ }
+ }
+ }
+ }
+ violations.each { fqcn, classViolations ->
+ def errors = classViolations.keySet()
+ def severities = errors.collect { "severity-${it}" }
+ div(class: "panel panel-default ${severities.join(' ')}") {
+ div(class: "panel-heading") {
+ h3(class: 'panel-title', "Class $fqcn")
+ }
+ div(class: 'panel-body') {
+ table(class: "table table-striped table-bordered") {
+ tbody {
+ classViolations.each { err, list ->
+ list.each { item ->
+ tr(class: "bincompat-error severity-${err}") {
+ td {
+ h4 {
+ span(class: "label label-${severityMapping[err]}", err.capitalize())
+ }
+ }
+ td { span(item) }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ script(src: "http://code.jquery.com/jquery-1.11.0.min.js") {}
+ script(src: "http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js") {}
+ script {
+ yieldUnescaped '''
+$(document).ready(function () {
+ var severity = null;
+ doFilter();
+ function doFilter() {
+ var severityClass = "severity-" + severity;
+ $('.panel').hide();
+ $('.bincompat-error').hide();
+ $('.bincompat-error').filter(function () {
+ return (severity==null || $(this).hasClass(severityClass));
+ }).show();
+ $('.panel').filter(function () {
+ return (severity==null || $(this).hasClass(severityClass));
+ }).show();
+ }
+ $(".dropdown-severity li a").click(function() {
+ severity = $(this).text().toLowerCase();
+ if (severity==="all levels") {
+ severity = null;
+ }
+ doFilter();
+ });
+});'''
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/json-path/build.gradle b/json-path/build.gradle
index caf7fa1e..15bdaaab 100644
--- a/json-path/build.gradle
+++ b/json-path/build.gradle
@@ -1,5 +1,6 @@
apply from: "$rootDir/gradle/publishMaven.gradle"
+
displayName = "Json Path"
description = "Java port of Stefan Goessner JsonPath."
@@ -13,9 +14,9 @@ jar {
dependencies {
compile libs.jsonSmart
+ compile libs.slf4jApi
compile libs.jacksonDatabind, optional
compile libs.gson, optional
- compile libs.slf4jApi
testCompile libs.test
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/mapper/JsonSmartMappingProvider.java b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/mapper/JsonSmartMappingProvider.java
index 8f3751f1..bd02d67f 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/spi/mapper/JsonSmartMappingProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/spi/mapper/JsonSmartMappingProvider.java
@@ -42,9 +42,13 @@ public class JsonSmartMappingProvider implements MappingProvider {
static {
DEFAULT.registerReader(Long.class, new LongReader());
+ DEFAULT.registerReader(long.class, new LongReader());
DEFAULT.registerReader(Integer.class, new IntegerReader());
+ DEFAULT.registerReader(int.class, new IntegerReader());
DEFAULT.registerReader(Double.class, new DoubleReader());
+ DEFAULT.registerReader(double.class, new DoubleReader());
DEFAULT.registerReader(Float.class, new FloatReader());
+ DEFAULT.registerReader(float.class, new FloatReader());
DEFAULT.registerReader(BigDecimal.class, new BigDecimalReader());
DEFAULT.registerReader(String.class, new StringReader());
DEFAULT.registerReader(Date.class, new DateReader());
@@ -94,7 +98,7 @@ public class JsonSmartMappingProvider implements MappingProvider {
@Override
public T map(Object source, TypeRef targetType, Configuration configuration) {
- throw new UnsupportedOperationException("Not supported! Use Jackson or Gson provider");
+ throw new UnsupportedOperationException("Json-smart provider does not support TypeRef! Use a Jackson or Gson based provider");
}
private static class StringReader extends JsonReaderI {
@@ -116,7 +120,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
- if (Long.class.isAssignableFrom(src.getClass())) {
+ if(Integer.class.isAssignableFrom(src.getClass())){
+ return (Integer) src;
+ } else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).intValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).intValue();
@@ -138,7 +144,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
- if (Integer.class.isAssignableFrom(src.getClass())) {
+ if(Long.class.isAssignableFrom(src.getClass())){
+ return (Long) src;
+ } else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).longValue();
} else if (Double.class.isAssignableFrom(src.getClass())) {
return ((Double) src).longValue();
@@ -160,7 +168,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
- if (Integer.class.isAssignableFrom(src.getClass())) {
+ if(Double.class.isAssignableFrom(src.getClass())){
+ return (Double) src;
+ } else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).doubleValue();
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).doubleValue();
@@ -182,7 +192,9 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
- if (Integer.class.isAssignableFrom(src.getClass())) {
+ if(Float.class.isAssignableFrom(src.getClass())){
+ return (Float) src;
+ } else if (Integer.class.isAssignableFrom(src.getClass())) {
return ((Integer) src).floatValue();
} else if (Long.class.isAssignableFrom(src.getClass())) {
return ((Long) src).floatValue();
@@ -215,10 +227,11 @@ public class JsonSmartMappingProvider implements MappingProvider {
if(src == null){
return null;
}
- if(Long.class.isAssignableFrom(src.getClass())){
+ if(Date.class.isAssignableFrom(src.getClass())){
+ return (Date) src;
+ } else if(Long.class.isAssignableFrom(src.getClass())){
return new Date((Long) src);
- }
- else if(String.class.isAssignableFrom(src.getClass())){
+ } else if(String.class.isAssignableFrom(src.getClass())){
try {
return DateFormat.getInstance().parse(src.toString());
} catch (ParseException e) {
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java
new file mode 100644
index 00000000..67a118f2
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java
@@ -0,0 +1,91 @@
+package com.jayway.jsonpath;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static com.jayway.jsonpath.JsonPath.using;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JsonProviderTest extends BaseTest {
+
+ private static final String JSON =
+ "[" +
+ "{\n" +
+ " \"foo\" : \"foo0\",\n" +
+ " \"bar\" : 0,\n" +
+ " \"baz\" : true,\n" +
+ " \"gen\" : {\"prop\" : \"yepp0\"}" +
+ "}," +
+ "{\n" +
+ " \"foo\" : \"foo1\",\n" +
+ " \"bar\" : 1,\n" +
+ " \"baz\" : true,\n" +
+ " \"gen\" : {\"prop\" : \"yepp1\"}" +
+ "}," +
+ "{\n" +
+ " \"foo\" : \"foo2\",\n" +
+ " \"bar\" : 2,\n" +
+ " \"baz\" : true,\n" +
+ " \"gen\" : {\"prop\" : \"yepp2\"}" +
+ "}" +
+ "]";
+
+ @Test
+ public void strings_are_unwrapped() {
+ assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
+ assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
+ assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
+ assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.string-property", String.class)).isEqualTo("string-value");
+ }
+
+ @Test
+ public void integers_are_unwrapped() {
+ assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", Integer.class)).isEqualTo(Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void ints_are_unwrapped() {
+ assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(using(JSON_SMART_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.int-max-property", int.class)).isEqualTo(Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void list_of_numbers() {
+
+ TypeRef> typeRef = new TypeRef>() {};
+
+ assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
+ assertThat(using(JACKSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
+ assertThat(using(GSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$.store.book[*].display-price", typeRef)).containsExactly(8.95D, 12.99D, 8.99D, 22.99D);
+ }
+
+ @Test
+ public void test_type_ref() throws IOException {
+ TypeRef>> typeRef = new TypeRef>>() {};
+
+ assertThat(using(JACKSON_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
+ assertThat(using(JACKSON_TREE_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
+ assertThat(using(GSON_CONFIGURATION).parse(JSON).read("$", typeRef)).extracting("foo").containsExactly("foo0", "foo1", "foo2");
+ }
+
+
+ public static class FooBarBaz {
+ public T gen;
+ public String foo;
+ public Long bar;
+ public boolean baz;
+ }
+
+
+ public static class Sub {
+ public String prop;
+ }
+
+}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonSmartMappingProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonSmartMappingProviderTest.java
deleted file mode 100644
index 7c7b4ad9..00000000
--- a/json-path/src/test/java/com/jayway/jsonpath/JsonSmartMappingProviderTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.jayway.jsonpath;
-
-import org.junit.Test;
-
-import java.io.IOException;
-import java.util.List;
-
-import static com.jayway.jsonpath.JsonPath.parse;
-import static com.jayway.jsonpath.JsonPath.using;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class JsonSmartMappingProviderTest {
-
- private static final String JSON =
- "[" +
- "{\n" +
- " \"foo\" : \"foo0\",\n" +
- " \"bar\" : 0,\n" +
- " \"baz\" : true,\n" +
- " \"gen\" : {\"eric\" : \"yepp\"}" +
- "}," +
- "{\n" +
- " \"foo\" : \"foo1\",\n" +
- " \"bar\" : 1,\n" +
- " \"baz\" : true,\n" +
- " \"gen\" : {\"eric\" : \"yepp\"}" +
- "}," +
- "{\n" +
- " \"foo\" : \"foo2\",\n" +
- " \"bar\" : 2,\n" +
- " \"baz\" : true,\n" +
- " \"gen\" : {\"eric\" : \"yepp\"}" +
- "}" +
- "]";
-
- @Test
- public void class_mapping() {
-
- Object map = Configuration.defaultConfiguration().jsonProvider().createMap();
- Configuration.defaultConfiguration().jsonProvider().setProperty(map, "eric", "eric-val");
-
-
- Gen gen = parse(map).read("$", Gen.class);
-
- assertThat(gen.eric).isEqualTo("eric-val");
-
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void test_type_ref() throws IOException {
-
- TypeRef> typeRef = new TypeRef>() {};
-
- List gen = parse(JSON).read("$", typeRef);
- }
-
-
- public static class FooBarBaz {
- public Gen gen;
- public String foo;
- public Long bar;
- public boolean baz;
- }
-
-
- public static class Gen {
- public String eric;
- }
-}