Added equality check to matchers

This commit is contained in:
Marcin Grzejszczak
2017-01-11 14:16:53 +01:00
parent 9ccc5bc0fd
commit 76966a985c
12 changed files with 172 additions and 60 deletions

View File

@@ -255,6 +255,8 @@ part of your contract. The other is called `testMatchers` which is present in th
Currently we support only JSON Path based matchers with the following matching possibilities.
For `stubMatchers`:
- `byValue(...)` - the value taken from the response via the provided JSON Path needs
to be equal to the provided value
- `byRegex(...)` - the value taken from the response via the provided JSON Path needs
to match the regex
- `byDate()` - the value taken from the response via the provided JSON Path needs to
@@ -266,6 +268,8 @@ match the regex for ISO Time
For `testMatchers`:
- `byValue(...)` - the value taken from the response via the provided JSON Path needs
to be equal to the provided value
- `byRegex(...)` - the value taken from the response via the provided JSON Path needs
to match the regex
- `byDate()` - the value taken from the response via the provided JSON Path needs to
@@ -307,36 +311,38 @@ assertions and the one from matchers with an `and` section):
[source,java,indent=0]
----
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/json")
.body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}");
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/json")
.body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}");
// when:
ResponseOptions response = given().spec(request)
.get("/get");
// when:
ResponseOptions response = given().spec(request)
.get("/get");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo");
// and:
assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}");
assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*");
assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?");
assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)");
assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])");
assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(class java.lang.String.class);
assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class);
assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class).size()).isLessThanOrEqualTo(1);
assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class);
assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class).size()).isGreaterThanOrEqualTo(3);
assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class);
assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isStrictlyBetween(1, 3);
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo");
// and:
assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}");
assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123);
assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*");
assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc");
assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?");
assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)");
assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])");
assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class);
assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class);
assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class).size()).isLessThanOrEqualTo(1);
assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class);
assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class).size()).isGreaterThanOrEqualTo(3);
assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class);
assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isStrictlyBetween(1, 3);
----
and the WireMock stub like this:

View File

@@ -25,7 +25,7 @@ interface BodyMatcher {
* by type, the defined response body contained an integer but the actual one
* contained a string then the assertion should fail
*/
String value()
Object value()
/**
* Min no of occurrence when matching by type. In all other cases it will be ignored

View File

@@ -46,6 +46,11 @@ class BodyMatchers {
return new MatchingTypeValue(MatchingType.REGEX, regex)
}
MatchingTypeValue byValue(Object value) {
assert value
return new MatchingTypeValue(MatchingType.EQUALITY, value)
}
boolean equals(o) {
if (this.is(o)) return true
if (this.getClass() != o.class) return false
@@ -81,7 +86,7 @@ class JsonPathBodyMatcher implements BodyMatcher {
}
@Override
String value() {
Object value() {
return this.matchingTypeValue.value
}
@@ -106,9 +111,9 @@ class MatchingTypeValue {
MatchingType type
/**
* Value of regular expression
* Value to check
*/
String value
Object value
/**
* Min occurrence when matching by type

View File

@@ -77,7 +77,7 @@ class StubRunnerCamelPredicate implements Predicate {
}
if (matchers != null && matchers.hasMatchers()) {
for (BodyMatcher matcher : matchers.jsonPathMatchers()) {
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher.path(), matcher.value());
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher);
matches &= matchesJsonPath(parsedJson, jsonPath);
}
}

View File

@@ -76,7 +76,7 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector {
}
if (matchers != null && matchers.hasMatchers()) {
for (BodyMatcher matcher : matchers.jsonPathMatchers()) {
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher.path(), matcher.value());
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher);
matches &= matchesJsonPath(parsedJson, jsonPath);
}
}

View File

@@ -76,7 +76,7 @@ class StubRunnerStreamMessageSelector implements MessageSelector {
}
if (matchers != null && matchers.hasMatchers()) {
for (BodyMatcher matcher : matchers.jsonPathMatchers()) {
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher.path(), matcher.value());
String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher);
matches &= matchesJsonPath(parsedJson, jsonPath);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.contract.verifier.wiremock
import com.github.tomakehurst.wiremock.matching.RegexPattern
import com.github.tomakehurst.wiremock.stubbing.StubMapping
import org.junit.Rule
import org.junit.rules.TemporaryFolder
@@ -24,6 +25,8 @@ import org.springframework.cloud.contract.verifier.file.ContractMetadata
import spock.lang.Issue
import spock.lang.Specification
import java.util.regex.Pattern
class DslToWireMockClientConverterSpec extends Specification {
@Rule
@@ -51,6 +54,8 @@ class DslToWireMockClientConverterSpec extends Specification {
JSONAssert.assertEquals('''
{"request":{"method":"PUT","urlPattern":"/[0-9]{2}"},"response":{"status":200}}
''', json, false)
and:
stubMappingIsValidWireMockStub(json)
}
@Issue("196")
@@ -82,6 +87,8 @@ class DslToWireMockClientConverterSpec extends Specification {
"status":200,"fixedDelayMilliseconds":1000
}}
''', json, false)
and:
stubMappingIsValidWireMockStub(json)
}
def "should convert DSL file with a nested list to WireMock JSON"() {
@@ -186,6 +193,8 @@ class DslToWireMockClientConverterSpec extends Specification {
}
}
''', json, false)
and:
stubMappingIsValidWireMockStub(json)
}
@@ -222,6 +231,8 @@ class DslToWireMockClientConverterSpec extends Specification {
JSONAssert.assertEquals('''
{"request":{"urlPath":"/foos","method":"GET"},"response":{"body":"[{\\"id\\":\\"123\\"},{\\"id\\":\\"567\\"}]"}}
''', json, false)
and:
stubMappingIsValidWireMockStub(json)
}
@@ -248,11 +259,12 @@ class DslToWireMockClientConverterSpec extends Specification {
""")
when:
String json = converter.convertContent("test", new ContractMetadata(file.toPath(), false, 0, null))
StubMapping.buildFrom(json)
then:
noExceptionThrown()
and:
!json.contains('cursor')
and:
stubMappingIsValidWireMockStub(json)
}
def 'should convert dsl to wiremock to show it in the docs'() {
@@ -287,9 +299,9 @@ class DslToWireMockClientConverterSpec extends Specification {
}
''')
when:
String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null))
String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null))
then:
JSONAssert.assertEquals( // tag::wiremock[]
JSONAssert.assertEquals( // tag::wiremock[]
'''
{
"request" : {
@@ -318,14 +330,16 @@ class DslToWireMockClientConverterSpec extends Specification {
'''
// end::wiremock[]
, json, false)
and:
stubMappingIsValidWireMockStub(json)
}
def 'should convert dsl to wiremock with stub matchers'() {
given:
def converter = new DslToWireMockClientConverter()
def converter = new DslToWireMockClientConverter()
and:
File file = tmpFolder.newFile("dsl_from_docs.groovy")
file.write('''
File file = tmpFolder.newFile("dsl_from_docs.groovy")
file.write('''
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'GET'
@@ -357,7 +371,9 @@ class DslToWireMockClientConverterSpec extends Specification {
])
stubMatchers {
jsonPath('$.duck', byRegex("[0-9]{3}"))
jsonPath('$.duck', byValue(123))
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byValue("abc"))
jsonPath('$.number', byRegex(number()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
jsonPath('$.date', byDate())
@@ -394,8 +410,10 @@ class DslToWireMockClientConverterSpec extends Specification {
testMatchers {
// asserts the jsonpath value against manual regex
jsonPath('$.duck', byRegex("[0-9]{3}"))
jsonPath('$.duck', byValue(123))
// asserts the jsonpath value against some default regex
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byValue("abc"))
jsonPath('$.number', byRegex(number()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
// asserts vs inbuilt time related regex
@@ -425,9 +443,9 @@ class DslToWireMockClientConverterSpec extends Specification {
}
''')
when:
String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null))
String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null))
then:
JSONAssert.assertEquals(//tag::matchers[]
JSONAssert.assertEquals(//tag::matchers[]
'''
{
"request" : {
@@ -450,8 +468,12 @@ class DslToWireMockClientConverterSpec extends Specification {
"matchesJsonPath" : "$.list.someother.nested[?(@.json == 'with value')]"
}, {
"matchesJsonPath" : "$[?(@.duck =~ /([0-9]{3})/)]"
}, {
"matchesJsonPath" : "$[?(@.duck == 123)]"
}, {
"matchesJsonPath" : "$[?(@.alpha =~ /([\\\\p{L}]*)/)]"
}, {
"matchesJsonPath" : "$[?(@.alpha == 'abc')]"
}, {
"matchesJsonPath" : "$[?(@.number =~ /(-?\\\\d*(\\\\.\\\\d+)?)/)]"
}, {
@@ -477,6 +499,8 @@ class DslToWireMockClientConverterSpec extends Specification {
'''
//end::matchers[]
, json, false)
and:
stubMappingIsValidWireMockStub(json)
}
def 'should convert dsl to wiremock with stub matchers with docs example'() {
@@ -549,6 +573,16 @@ class DslToWireMockClientConverterSpec extends Specification {
}
'''
, json, false)
and:
stubMappingIsValidWireMockStub(json)
}
void stubMappingIsValidWireMockStub(String mappingDefinition) {
StubMapping stubMapping = StubMapping.buildFrom(mappingDefinition)
stubMapping.request.bodyPatterns.findAll { it.isPresent() && it instanceof RegexPattern }.every {
Pattern.compile(it.getValue())
}
assert !mappingDefinition.contains('org.springframework.cloud.contract.spec.internal')
}
}

View File

@@ -312,7 +312,11 @@ abstract class MethodBodyBuilder {
// for the rest we'll do JsonPath matching in brute force
bodyMatchers.jsonPathMatchers().each {
if (it.value()) {
String method = "assertThat(parsedJson.read(${quotedAndEscaped(it.path())}, String.class)).matches(${quotedAndEscaped(it.value())})"
String comparisonMethod = it.matchingType() == MatchingType.EQUALITY ? "isEqualTo" : "matches"
String valueAsParam = it.value() instanceof String ? quotedAndEscaped(it.value().toString()) : it.value().toString()
String classToCastTo = "${it.value().class.simpleName}.class"
String path = quotedAndEscaped(it.path())
String method = "assertThat(parsedJson.read(${path}, ${classToCastTo})).${comparisonMethod}(${valueAsParam})"
bb.addLine(postProcessJsonPathCall(method))
addColonIfRequired(bb)
} else {

View File

@@ -87,7 +87,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
}
if (request.matchers?.hasMatchers()) {
request.matchers.jsonPathMatchers().each {
String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it.path(), it.value())
String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it)
requestPattern.withRequestBody(WireMock.matchingJsonPath(newPath.replace("\\\\", "\\")))
}
}

View File

@@ -21,15 +21,14 @@ import com.toomuchcoding.jsonassert.JsonAssertion
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.util.logging.Slf4j
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.spec.internal.BodyMatcher
import org.springframework.cloud.contract.spec.internal.BodyMatchers
import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
import org.springframework.cloud.contract.spec.internal.MatchingType
import org.springframework.cloud.contract.spec.internal.OptionalProperty
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties
import java.util.regex.Pattern
/**
* I would like to apologize to anyone who is reading this class. Since JSON is a hectic structure
* this class is also hectic. The idea is to traverse the JSON structure and build a set of
@@ -73,8 +72,7 @@ class JsonToJsonPathsConverter {
*/
static def removeMatchingJsonPaths(def json, BodyMatchers bodyMatchers) {
if (bodyMatchers?.hasMatchers()) {
// remove all jsonpaths from the body - for those that remain we continue as usual
bodyMatchers.jsonPathMatchers().findAll { it.matchingType() != MatchingType.EQUALITY }.each { BodyMatcher matcher ->
bodyMatchers.jsonPathMatchers().each { BodyMatcher matcher ->
JsonPath.parse(json).delete(matcher.path())
}
}
@@ -82,21 +80,32 @@ class JsonToJsonPathsConverter {
}
/**
* For the given JSON path and regex pattern converts it into a JSON path
* that checks the Pattern
* For the given matcher converts it into a JSON path
* that checks the regex pattern or equality
*
* @param path - JSON path
* @param pattern - pattern to check for the last element of JSON path
* @param bodyMatcher
* @return JSON path that checks the regex for its last element
*/
static String convertJsonPathAndRegexToAJsonPath(String path, String pattern) {
if (!pattern) {
static String convertJsonPathAndRegexToAJsonPath(BodyMatcher bodyMatcher) {
String path = bodyMatcher.path()
Object value = bodyMatcher.value()
if (!value) {
return path
}
int lastIndexOfDot = path.lastIndexOf(".")
String toLastDot = path.substring(0, lastIndexOfDot)
String fromLastDot = path.substring(lastIndexOfDot + 1)
return "${toLastDot}[?(@.${fromLastDot} =~ /(${pattern})/)]"
String comparison = createComparison(bodyMatcher, value)
return "${toLastDot}[?(@.${fromLastDot} ${comparison})]"
}
private static String createComparison(BodyMatcher bodyMatcher, Object value) {
if (bodyMatcher.matchingType() == MatchingType.EQUALITY) {
String wrappedValue = value instanceof Number ? value : "'${value.toString()}'"
return "== ${wrappedValue}"
} else {
return "=~ /(${value})/"
}
}
JsonPaths transformToJsonPathWithTestsSideValues(def json) {

View File

@@ -51,7 +51,9 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
])
stubMatchers {
jsonPath('$.duck', byRegex("[0-9]{3}"))
jsonPath('$.duck', byValue(123))
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byValue("abc"))
jsonPath('$.number', byRegex(number()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
jsonPath('$.date', byDate())
@@ -87,8 +89,11 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
testMatchers {
// asserts the jsonpath value against manual regex
jsonPath('$.duck', byRegex("[0-9]{3}"))
// asserts the jsonpath value against the provided value
jsonPath('$.duck', byValue(123))
// asserts the jsonpath value against some default regex
jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
jsonPath('$.alpha', byValue("abc"))
jsonPath('$.number', byRegex(number()))
jsonPath('$.aBoolean', byRegex(anyBoolean()))
// asserts vs inbuilt time related regex
@@ -124,7 +129,9 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
def test = blockBuilder.toString()
then:
test.contains('assertThat(parsedJson.read("' + rootElement + '.duck", String.class)).matches("[0-9]{3}")')
test.contains('assertThat(parsedJson.read("' + rootElement + '.duck", Integer.class)).isEqualTo(123)')
test.contains('assertThat(parsedJson.read("' + rootElement + '.alpha", String.class)).matches("[\\\\p{L}]*")')
test.contains('assertThat(parsedJson.read("' + rootElement + '.alpha", String.class)).isEqualTo("abc")')
test.contains('assertThat(parsedJson.read("' + rootElement + '.number", String.class)).matches("-?\\\\d*(\\\\.\\\\d+)?")')
test.contains('assertThat(parsedJson.read("' + rootElement + '.aBoolean", String.class)).matches("(true|false)")')
test.contains('assertThat(parsedJson.read("' + rootElement + '.date", String.class)).matches("(\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")')

View File

@@ -24,6 +24,8 @@ import com.toomuchcoding.jsonassert.JsonAssertion
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import net.minidev.json.JSONArray
import org.springframework.cloud.contract.spec.internal.BodyMatcher
import org.springframework.cloud.contract.spec.internal.MatchingType
import spock.lang.Specification
import spock.util.environment.RestoreSystemProperties
@@ -758,14 +760,59 @@ class JsonToJsonPathsConverterSpec extends Specification {
String jsonPath = '$.a.b.c.d'
String regexPattern = ".*"
expect:
'$.a.b.c[?(@.d =~ /(.*)/)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(jsonPath, regexPattern)
'$.a.b.c[?(@.d =~ /(.*)/)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.REGEX, jsonPath, regexPattern))
}
def "should return the path if no regex pattern is provided"() {
def "should convert a json path with value to a equality checking json path without quotes for numbers"() {
given:
String jsonPath = '$.a.b.c.d'
Integer value = 1234
expect:
'$.a.b.c[?(@.d == 1234)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, value))
}
def "should convert a json path with value to a equality checking json path with quotes for strings"() {
given:
String jsonPath = '$.a.b.c.d'
String value = "foo"
expect:
'$.a.b.c[?(@.d == \'foo\')]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, value))
}
def "should return the path if no value is provided"() {
given:
String jsonPath = '$.a.b.c.d'
expect:
'$.a.b.c.d' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(jsonPath, null)
'$.a.b.c.d' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.REGEX, jsonPath, null))
}
private BodyMatcher matcher(final MatchingType matchingType, final String jsonPath, final Object value) {
return new BodyMatcher() {
@Override
MatchingType matchingType() {
return matchingType
}
@Override
String path() {
return jsonPath
}
@Override
Object value() {
return value
}
@Override
Integer minTypeOccurrence() {
return null
}
@Override
Integer maxTypeOccurrence() {
return null
}
}
}
private void assertThatJsonPathsInMapAreValid(String json, JsonPaths pathAndValues) {