diff --git a/README.md b/README.md
index a39004be9..ceef258c2 100755
--- a/README.md
+++ b/README.md
@@ -186,6 +186,7 @@ And you don't need to create additional Java classes for any of the payloads tha
| match contains only deep
| match !contains
| match each
+ | match each contains deep
| match header
| Fuzzy Matching
| Schema Validation
@@ -3017,6 +3018,42 @@ Symbol | Evaluates To
There is a shortcut for `match each` explained in the next section that can be quite useful, especially for 'in-line' schema-like validations.
+#### `match each contains deep`
+`match each` can be combined with `contains deep` so that for each JSON object a “deep contains” match is performed within nested lists or objects.
+
+This is useful for testing payloads with JSON arrays whose members have a few essential keys that you wish to validate.
+
+```cucumber
+ Given def response =
+ """
+ [
+ {
+ "a": 1,
+ "arr": [
+ {
+ "b": 2,
+ "c": 3
+ }
+ ]
+ },
+ {
+ "a": 1,
+ "arr": [
+ {
+ "b": 2,
+ "c": 3
+ },
+ {
+ "b": 4,
+ "c": 5
+ }
+ ]
+ }
+ ]
+ """
+ Then match each response contains deep { a: 1, arr: [ { b: 2 } ] }
+```
+
## Schema Validation
Karate provides a far more simpler and more powerful way than [JSON-schema](http://json-schema.org) to validate the structure of a given payload. You can even mix domain and conditional validations and perform all assertions in a single step.
diff --git a/karate-core/src/main/java/com/intuit/karate/MatchStep.java b/karate-core/src/main/java/com/intuit/karate/MatchStep.java
index f5670af9b..ab6db5565 100644
--- a/karate-core/src/main/java/com/intuit/karate/MatchStep.java
+++ b/karate-core/src/main/java/com/intuit/karate/MatchStep.java
@@ -139,6 +139,12 @@ private static Match.Type getType(boolean each, boolean not, boolean contains, b
if (any) {
return Match.Type.EACH_CONTAINS_ANY;
}
+ if (deep) {
+ if (not) {
+ throw new RuntimeException("'each !contains deep' is not yet supported, use 'each contains deep' instead");
+ }
+ return Match.Type.EACH_CONTAINS_DEEP;
+ }
return not ? Match.Type.EACH_NOT_CONTAINS : Match.Type.EACH_CONTAINS;
}
return not ? Match.Type.EACH_NOT_EQUALS : Match.Type.EACH_EQUALS;
diff --git a/karate-core/src/test/java/com/intuit/karate/MatchStepTest.java b/karate-core/src/test/java/com/intuit/karate/MatchStepTest.java
index 3948205ce..dad9fce9b 100644
--- a/karate-core/src/test/java/com/intuit/karate/MatchStepTest.java
+++ b/karate-core/src/test/java/com/intuit/karate/MatchStepTest.java
@@ -25,6 +25,7 @@ void testMatchStep() {
test("hello world == foo", EQUALS, "hello", "world", "foo");
test("hello world contains only deep foo", CONTAINS_ONLY_DEEP, "hello", "world", "foo");
test("each hello world == foo", EACH_EQUALS, "hello", "world", "foo");
+ test("each hello world contains deep foo", EACH_CONTAINS_DEEP, "hello", "world", "foo");
test("{\"a\":1,\"b\":2} == '#object'", EQUALS, "({\"a\":1,\"b\":2})", null, "'#object'");
test("hello.foo(bar) != blah", NOT_EQUALS, "hello.foo(bar)", null, "blah");
test("foo count(/records//record) contains any blah", CONTAINS_ANY, "foo", "count(/records//record)", "blah");
diff --git a/karate-core/src/test/java/com/intuit/karate/core/ScenarioRuntimeTest.java b/karate-core/src/test/java/com/intuit/karate/core/ScenarioRuntimeTest.java
index 59c2c9c32..90d2f7d44 100644
--- a/karate-core/src/test/java/com/intuit/karate/core/ScenarioRuntimeTest.java
+++ b/karate-core/src/test/java/com/intuit/karate/core/ScenarioRuntimeTest.java
@@ -765,7 +765,15 @@ void testMatchContainsOnlyDeep() {
"def response = { foo: [ 'a', 'b' ] } ",
"match response contains only deep { foo: [ 'b', 'a' ] }"
);
- }
+ }
+
+ @Test
+ void testMatchEachContainsDeep() {
+ run(
+ "def response = [ { a: 1, arr: [ { b: 2, c: 3 }, { b: 4, c: 5 } ] } ]",
+ "match each response contains deep { a: 1, arr: [ { b: '#number', c: 3 } ] }"
+ );
+ }
@Test
void testJavaInteropStatic() {