package org.mongodb.morphia.testutil;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.json.JSONException;
import org.junit.Assert;
import org.skyscreamer.jsonassert.JSONCompare;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONParser;
/**
* Matcher that allows comparison between Strings as JSON Strings. This lets you be a bit more flexible when comparing JSON strings,
* instead of having to compare String to String with exact matches (including unnecessary spaces). So instead of having to specify every
* quotation and exact spacing like this:
* <pre>
* {@code "{ \"fieldName\" : { \"anotherName\" : 99}}"}
* </pre>
* you can pass in the expected value as a String and have the JSON parser compare it with a valid JSON value. For example:
* <pre>
* {@code assertThat("{'keywords':{'$size':4}}", jsonEqual("{ keywords: { $size: 4 } }")); }
* </pre>
* will pass.
* <p/>
* Note that if either the expected JSON or actual JSON values are invalid JSON strings, the matcher will fail, but not throw an Exception.
* The underlying JSON parse exception will be displayed in the failure message.
*/
public class JSONMatcher extends TypeSafeMatcher<String> {
private final String expectedJson;
/**
* Create a new instance of the matcher with the given String as the expected JSON value to match.
*
* @param expectedJson a String containing valid JSON that represents the expected value to compare against.
*/
public JSONMatcher(final String expectedJson) {
this.expectedJson = expectedJson;
}
/**
* Static helper to return a Matcher that compares the given String as a JSON value.
*
* @param expectedValue the expected JSON value as a String. Things like quotation marks are optional on field names
* @return a JSONMatcher that will can be used to compare this expected value with an actual value.
*/
public static Matcher<String> jsonEqual(final String expectedValue) {
return new JSONMatcher(expectedValue);
}
@Override
public void describeTo(final Description description) {
try {
Object expectedJsonAsPrettyJson = JSONParser.parseJSON(expectedJson);
description.appendValue(expectedJsonAsPrettyJson);
} catch (final JSONException e) {
throw new RuntimeException(String.format("Error parsing expected JSON string %s. Got Exception %s",
expectedJson, e.toString()));
}
}
/**
* Compares the item (which represents the actual result) against the expected value to see if they are the same JSON. These two
* values
* may not be equal as Strings, but the JSON comparison should ignore unnecessary whitespace and the differences between " and '.
*
* @param item a String containing valid JSON to compare against the expected value
* @return true if the expected and actual values are equivalent JSON values
*/
@Override
public boolean matchesSafely(final String item) {
try {
return JSONCompare.compareJSON(expectedJson, item, JSONCompareMode.STRICT).passed();
} catch (final JSONException e) {
Assert.fail(String.format("JSON compare threw exception when trying to compare %n %s %n with %n %s%n Exception was: %n%s ",
expectedJson, item, e));
return false;
}
}
@Override
protected void describeMismatchSafely(final String item, final Description mismatchDescription) {
try {
Object itemAsPrettyJson = JSONParser.parseJSON(item);
mismatchDescription.appendText("was ").appendValue(itemAsPrettyJson);
} catch (final JSONException e) {
throw new RuntimeException(String.format("Error parsing expected JSON string %s. Got Exception %s",
item, e.toString()));
}
}
}