Skip to content

JSON Unit

Jörg Flade edited this page Jun 24, 2026 · 2 revisions

JSON-Unit

Built-in Matchers

Use these placeholders inside expected JSON (both in files and inline doc-strings) to validate dynamic response fields without knowing their exact value:

Matcher What it validates
${json-unit.matches:isValidUUID} Value is a valid UUID
${json-unit.matches:isValidDate} Value is parseable as a date
${json-unit.matches:isValidIBAN} Value is a valid IBAN
${json-unit.matches:isNotEqualTo}other string Value does not equal the string after the closing }
${json-unit.matches:isDateOfContext}<var> Value equals the date stored in ScenarioContext under <var> (date portion only)
${json-unit.matches:isEqualToScenarioContext}<var> Value equals the string stored in ScenarioContext under <var>
${json-unit.matches:isNotEqualToScenarioContext}<var> Value does not equal the string stored in ScenarioContext under <var>

Example expected JSON file:

{
  "id": "${json-unit.matches:isValidUUID}",
  "name": "Alice",
  "createdAt": "${json-unit.matches:isValidDate}",
  "iban": "${json-unit.matches:isValidIBAN}",
  "previousId": "${json-unit.matches:isNotEqualToScenarioContext}CREATED_ID"
}

Beyond these matchers, JSON-Unit itself supports regex comparison, type placeholders, ignoring values/elements/paths, and more.

Note: Only unparameterized custom matchers or bdd lib matchers can be used for single-field validation (I ensure that the body of the response contains a field … with the value …). Parameterized matchers require full-body file comparison.

Examples: src/test/resources/features/body_validation/field_compare.feature

Flexible Comparison

By default, JSON comparison is strict: extra fields, extra array elements, and array ordering all cause failures. Use the following steps (or annotations) to relax specific aspects.

Ignore extra array elements

Given that the response JSON can contain arrays with extra elements

The comparison passes even if the response array has more elements than the expected JSON.

Annotation alternative (on Feature or Scenario):

@bdd_lib_json_ignore_new_array_elements

Ignore array order

Given that the response JSON can contain arrays in different order

The comparison ignores the order of array elements.

Annotation alternative:

@bdd_lib_json_ignore_array_order

Ignore extra fields

Given that the response JSON can contain extra fields

The comparison passes even if the response contains fields not listed in the expected JSON.

Annotation alternative:

@bdd_lib_json_ignore_extra_fields

Examples: src/test/resources/features/flexible_json/

Custom Matchers

You can write your own JSON-Unit matchers and register them with the library. A matcher must extend org.hamcrest.BaseMatcher and implement com.ragin.bdd.cucumber.matcher.BddCucumberJsonMatcher.

Simple matcher

import com.ragin.bdd.cucumber.matcher.BddCucumberJsonMatcher;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.springframework.stereotype.Component;

@Component
public class DividableByTwoMatcher extends BaseMatcher<Object> implements BddCucumberJsonMatcher {

    @Override
    public boolean matches(Object item) {
        if (StringUtils.isNumeric(String.valueOf(item))) {
            return Integer.parseInt((String) item) % 2 == 0;
        }
        return false;
    }

    @Override
    public void describeTo(Description description) {}

    @Override
    public String matcherName() {
        return "isDividableByTwo";
    }

    @Override
    public Class<? extends BaseMatcher<?>> matcherClass() {
        return this.getClass();
    }
}

Use it in an expected JSON:

{
  "count": "${json-unit.matches:isDividableByTwo}"
}

Parameterized matcher

Implement net.javacrumbs.jsonunit.core.ParametrizedMatcher to receive a parameter. For multiple arguments, pass them as a JSON string.

import com.ragin.bdd.cucumber.matcher.BddCucumberJsonMatcher;
import net.javacrumbs.jsonunit.core.ParametrizedMatcher;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.springframework.stereotype.Component;

@Component
public class DividableByNumberMatcher extends BaseMatcher<Object>
        implements ParametrizedMatcher, BddCucumberJsonMatcher {

    private String parameter;

    @Override
    public boolean matches(Object item) {
        if (StringUtils.isNumeric(String.valueOf(item))) {
            return Integer.parseInt((String) item) % Integer.parseInt(parameter) == 0;
        }
        return false;
    }

    @Override
    public void describeTo(Description description) {}

    @Override
    public String matcherName() { return "isDividableByNumber"; }

    @Override
    public Class<? extends BaseMatcher<?>> matcherClass() { return this.getClass(); }

    @Override
    public void setParameter(String parameter) { this.parameter = parameter; }
}

Pass the parameter directly after the closing }:

{ "count": "${json-unit.matches:isDividableByNumber}5" }

For a JSON argument (use single quotes inside):

{ "count": "${json-unit.matches:isDividableByNumber}{\"divisor\": 5}" }

Extending the date validator

To add custom DateTimeFormatter patterns to ${json-unit.matches:isValidDate}, implement BddCucumberDateTimeFormat:

@Component
public class MyDateFormats implements BddCucumberDateTimeFormat {
    @Override
    public List<DateTimeFormatter> pattern() {
        return List.of(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
    }
}

Register the class in @ContextConfiguration (see below).

Registering matchers

Add custom matchers and formatters to the @ContextConfiguration classes in your test hooks:

@ContextConfiguration(classes = {
    DividableByTwoMatcher.class,
    DividableByNumberMatcher.class,
    MyDateFormats.class
})

See CreateContextHooks.java for a complete example. Custom matcher examples: src/test/java/com/ragin/bdd/cucumbertests/hooks/

Clone this wiki locally