Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fb86ca0

Browse filesBrowse files
committed
Merge pull request skyscreamer#22 from Aivean/master
Implemented skyscreamer#21: Add possibility to override/extend comparison behavior
2 parents 9987e27 + 34ec290 commit fb86ca0
Copy full SHA for fb86ca0

File tree

Expand file treeCollapse file tree

9 files changed

+523
-317
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

9 files changed

+523
-317
lines changed
Open diff view settings
Collapse file

‎src/main/java/org/skyscreamer/jsonassert/JSONAssert.java‎

Copy file name to clipboardExpand all lines: src/main/java/org/skyscreamer/jsonassert/JSONAssert.java
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.json.JSONArray;
44
import org.json.JSONException;
55
import org.json.JSONObject;
6+
import org.skyscreamer.jsonassert.comparator.JSONComparator;
67

78
/**
89
* <p>A set of assertion methods useful for writing tests methods that return JSON.</p>
@@ -97,6 +98,24 @@ public static void assertEquals(String expectedStr, String actualStr, boolean st
9798
}
9899
}
99100

101+
/**
102+
* Asserts that the json string provided matches the expected string. If it isn't it throws an
103+
* {@link AssertionError}.
104+
*
105+
* @param expectedStr Expected JSON string
106+
* @param actualStr String to compare
107+
* @param comparator Comparator
108+
* @throws JSONException
109+
*/
110+
public static void assertEquals(String expectedStr, String actualStr, JSONComparator comparator)
111+
throws JSONException
112+
{
113+
JSONCompareResult result = JSONCompare.compareJSON(expectedStr, actualStr, comparator);
114+
if (result.failed()) {
115+
throw new AssertionError(result.getMessage());
116+
}
117+
}
118+
100119
/**
101120
* Asserts that the JSONObject provided matches the expected JSONObject. If it isn't it throws an
102121
* {@link AssertionError}.
Collapse file

‎src/main/java/org/skyscreamer/jsonassert/JSONCompare.java‎

Copy file name to clipboardExpand all lines: src/main/java/org/skyscreamer/jsonassert/JSONCompare.java
+63-286Lines changed: 63 additions & 286 deletions
Large diffs are not rendered by default.
Collapse file

‎src/main/java/org/skyscreamer/jsonassert/JSONCompareResult.java‎

Copy file name to clipboardExpand all lines: src/main/java/org/skyscreamer/jsonassert/JSONCompareResult.java
+23-30Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313
public class JSONCompareResult {
1414
private boolean _success;
15-
private String _message;
15+
private StringBuilder _message;
1616
private String _field;
1717
private Object _expected;
1818
private Object _actual;
@@ -27,7 +27,7 @@ public JSONCompareResult() {
2727

2828
private JSONCompareResult(boolean success, String message) {
2929
_success = success;
30-
_message = message == null ? "" : message;
30+
_message = new StringBuilder(message == null ? "" : message);
3131
}
3232

3333
/**
@@ -51,7 +51,7 @@ public boolean failed() {
5151
* @return String explaining why if the comparison failed
5252
*/
5353
public String getMessage() {
54-
return _message;
54+
return _message.toString();
5555
}
5656

5757
/**
@@ -103,13 +103,12 @@ public String getField() {
103103
return _field;
104104
}
105105

106-
protected void fail(String message) {
106+
public void fail(String message) {
107107
_success = false;
108108
if (_message.length() == 0) {
109-
_message = message;
110-
}
111-
else {
112-
_message += " ; " + message;
109+
_message.append(message);
110+
} else {
111+
_message.append(" ; ").append(message);
113112
}
114113
}
115114

@@ -119,7 +118,7 @@ protected void fail(String message) {
119118
* @param expected Expected result
120119
* @param actual Actual result
121120
*/
122-
protected JSONCompareResult fail(String field, Object expected, Object actual) {
121+
public JSONCompareResult fail(String field, Object expected, Object actual) {
123122
_fieldFailures.add(new FieldComparisonFailure(field, expected, actual));
124123
this._field = field;
125124
this._expected = expected;
@@ -129,14 +128,12 @@ protected JSONCompareResult fail(String field, Object expected, Object actual) {
129128
}
130129

131130
private String formatFailureMessage(String field, Object expected, Object actual) {
132-
StringBuffer message= new StringBuffer();
133-
message.append(field);
134-
message.append("\nExpected: ");
135-
message.append(describe(expected));
136-
message.append("\n got: ");
137-
message.append(describe(actual));
138-
message.append("\n");
139-
return message.toString();
131+
return field
132+
+ "\nExpected: "
133+
+ describe(expected)
134+
+ "\n got: "
135+
+ describe(actual)
136+
+ "\n";
140137
}
141138

142139
public JSONCompareResult missing(String field, Object expected) {
@@ -145,12 +142,10 @@ public JSONCompareResult missing(String field, Object expected) {
145142
}
146143

147144
private String formatMissing(String field, Object expected) {
148-
StringBuffer message= new StringBuffer();
149-
message.append(field);
150-
message.append("\nExpected: ");
151-
message.append(describe(expected));
152-
message.append("\n but none found\n");
153-
return message.toString();
145+
return field
146+
+ "\nExpected: "
147+
+ describe(expected)
148+
+ "\n but none found\n";
154149
}
155150

156151
public JSONCompareResult unexpected(String field, Object value) {
@@ -159,12 +154,10 @@ public JSONCompareResult unexpected(String field, Object value) {
159154
}
160155

161156
private String formatUnexpected(String field, Object value) {
162-
StringBuffer message= new StringBuffer();
163-
message.append(field);
164-
message.append("\nUnexpected: ");
165-
message.append(describe(value));
166-
message.append("\n");
167-
return message.toString();
157+
return field
158+
+ "\nUnexpected: "
159+
+ describe(value)
160+
+ "\n";
168161
}
169162

170163
private static String describe(Object value) {
@@ -179,6 +172,6 @@ private static String describe(Object value) {
179172

180173
@Override
181174
public String toString() {
182-
return _message;
175+
return _message.toString();
183176
}
184177
}
Collapse file

‎src/main/java/org/skyscreamer/jsonassert/JSONParser.java‎

Copy file name to clipboardExpand all lines: src/main/java/org/skyscreamer/jsonassert/JSONParser.java
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ private JSONParser() {}
1515
* depending on whether the string represents an object or an array.
1616
*
1717
* @param s Raw JSON string to be parsed
18-
* @return
18+
* @return JSONObject or JSONArray
1919
* @throws JSONException
2020
*/
2121
public static Object parseJSON(String s) throws JSONException {
Collapse file
+163Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package org.skyscreamer.jsonassert.comparator;
2+
3+
import org.apache.commons.collections.CollectionUtils;
4+
import org.json.JSONArray;
5+
import org.json.JSONException;
6+
import org.json.JSONObject;
7+
import org.skyscreamer.jsonassert.JSONCompareResult;
8+
9+
import java.util.HashSet;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.*;
14+
15+
/**
16+
* This class provides a skeletal implementation of the {@link JSONComparator}
17+
* interface, to minimize the effort required to implement this interface. <p/>
18+
*/
19+
public abstract class AbstractComparator implements JSONComparator {
20+
21+
/**
22+
* Compares JSONObject provided to the expected JSONObject, and returns the results of the comparison.
23+
*
24+
* @param expected Expected JSONObject
25+
* @param actual JSONObject to compare
26+
* @throws JSONException
27+
*/
28+
@Override
29+
public final JSONCompareResult compareJSON(JSONObject expected, JSONObject actual) throws JSONException {
30+
JSONCompareResult result = new JSONCompareResult();
31+
compareJSON("", expected, actual, result);
32+
return result;
33+
}
34+
35+
/**
36+
* Compares JSONArray provided to the expected JSONArray, and returns the results of the comparison.
37+
*
38+
* @param expected Expected JSONArray
39+
* @param actual JSONArray to compare
40+
* @throws JSONException
41+
*/
42+
@Override
43+
public final JSONCompareResult compareJSON(JSONArray expected, JSONArray actual) throws JSONException {
44+
JSONCompareResult result = new JSONCompareResult();
45+
compareJSONArray("", expected, actual, result);
46+
return result;
47+
}
48+
49+
protected void checkJsonObjectKeysActualInExpected(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) {
50+
Set<String> actualKeys = getKeys(actual);
51+
for (String key : actualKeys) {
52+
if (!expected.has(key)) {
53+
result.unexpected(prefix, key);
54+
}
55+
}
56+
}
57+
58+
protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
59+
Set<String> expectedKeys = getKeys(expected);
60+
for (String key : expectedKeys) {
61+
Object expectedValue = expected.get(key);
62+
if (actual.has(key)) {
63+
Object actualValue = actual.get(key);
64+
compareValues(qualify(prefix, key), expectedValue, actualValue, result);
65+
} else {
66+
result.missing(prefix, key);
67+
}
68+
}
69+
}
70+
71+
protected void compareJSONArrayOfJsonObjects(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException {
72+
String uniqueKey = findUniqueKey(expected);
73+
if (uniqueKey == null || !isUsableAsUniqueKey(uniqueKey, actual)) {
74+
// An expensive last resort
75+
recursivelyCompareJSONArray(key, expected, actual, result);
76+
return;
77+
}
78+
Map<Object, JSONObject> expectedValueMap = arrayOfJsonObjectToMap(expected, uniqueKey);
79+
Map<Object, JSONObject> actualValueMap = arrayOfJsonObjectToMap(actual, uniqueKey);
80+
for (Object id : expectedValueMap.keySet()) {
81+
if (!actualValueMap.containsKey(id)) {
82+
result.missing(formatUniqueKey(key, uniqueKey, id), expectedValueMap.get(id));
83+
continue;
84+
}
85+
JSONObject expectedValue = expectedValueMap.get(id);
86+
JSONObject actualValue = actualValueMap.get(id);
87+
compareValues(formatUniqueKey(key, uniqueKey, id), expectedValue, actualValue, result);
88+
}
89+
for (Object id : actualValueMap.keySet()) {
90+
if (!expectedValueMap.containsKey(id)) {
91+
result.unexpected(formatUniqueKey(key, uniqueKey, id), actualValueMap.get(id));
92+
}
93+
}
94+
}
95+
96+
protected void compareJSONArrayOfSimpleValues(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException {
97+
@SuppressWarnings("unchecked")
98+
Map<Object, Integer> expectedCount = CollectionUtils.getCardinalityMap(jsonArrayToList(expected));
99+
@SuppressWarnings("unchecked")
100+
Map<Object, Integer> actualCount = CollectionUtils.getCardinalityMap(jsonArrayToList(actual));
101+
for (Object o : expectedCount.keySet()) {
102+
if (!actualCount.containsKey(o)) {
103+
result.missing(key + "[]", o);
104+
} else if (!actualCount.get(o).equals(expectedCount.get(o))) {
105+
result.fail(key + "[]: Expected " + expectedCount.get(o) + " occurrence(s) of " + o
106+
+ " but got " + actualCount.get(o) + " occurrence(s)");
107+
}
108+
}
109+
for (Object o : actualCount.keySet()) {
110+
if (!expectedCount.containsKey(o)) {
111+
result.unexpected(key + "[]", o);
112+
}
113+
}
114+
}
115+
116+
protected void compareJSONArrayWithStrictOrder(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException {
117+
for (int i = 0; i < expected.length(); ++i) {
118+
Object expectedValue = expected.get(i);
119+
Object actualValue = actual.get(i);
120+
compareValues(key + "[" + i + "]", expectedValue, actualValue, result);
121+
}
122+
}
123+
124+
// This is expensive (O(n^2) -- yuck), but may be the only resort for some cases with loose array ordering, and no
125+
// easy way to uniquely identify each element.
126+
// This is expensive (O(n^2) -- yuck), but may be the only resort for some cases with loose array ordering, and no
127+
// easy way to uniquely identify each element.
128+
protected void recursivelyCompareJSONArray(String key, JSONArray expected, JSONArray actual,
129+
JSONCompareResult result) throws JSONException {
130+
Set<Integer> matched = new HashSet<Integer>();
131+
for (int i = 0; i < expected.length(); ++i) {
132+
Object expectedElement = expected.get(i);
133+
boolean matchFound = false;
134+
for (int j = 0; j < actual.length(); ++j) {
135+
Object actualElement = actual.get(j);
136+
if (matched.contains(j) || !actualElement.getClass().equals(expectedElement.getClass())) {
137+
continue;
138+
}
139+
if (expectedElement instanceof JSONObject) {
140+
if (compareJSON((JSONObject) expectedElement, (JSONObject) actualElement).passed()) {
141+
matched.add(j);
142+
matchFound = true;
143+
break;
144+
}
145+
} else if (expectedElement instanceof JSONArray) {
146+
if (compareJSON((JSONArray) expectedElement, (JSONArray) actualElement).passed()) {
147+
matched.add(j);
148+
matchFound = true;
149+
break;
150+
}
151+
} else if (expectedElement.equals(actualElement)) {
152+
matched.add(j);
153+
matchFound = true;
154+
break;
155+
}
156+
}
157+
if (!matchFound) {
158+
result.fail(key + "[" + i + "] Could not find match for element " + expectedElement);
159+
return;
160+
}
161+
}
162+
}
163+
}
Collapse file
+70Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.skyscreamer.jsonassert.comparator;
2+
3+
import org.json.JSONArray;
4+
import org.json.JSONException;
5+
import org.json.JSONObject;
6+
import org.skyscreamer.jsonassert.JSONCompareMode;
7+
import org.skyscreamer.jsonassert.JSONCompareResult;
8+
9+
import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.allJSONObjects;
10+
import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.allSimpleValues;
11+
12+
/**
13+
* This class is the default json comparator implementation. <p/>
14+
* Comparison is performed according to {@link JSONCompareMode} that is passed as constructor's argument.
15+
*/
16+
public class DefaultComparator extends AbstractComparator {
17+
18+
JSONCompareMode mode;
19+
20+
public DefaultComparator(JSONCompareMode mode) {
21+
this.mode = mode;
22+
}
23+
24+
@Override
25+
public void compareJSON(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
26+
// Check that actual contains all the expected values
27+
checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
28+
29+
// If strict, check for vice-versa
30+
if (!mode.isExtensible()) {
31+
checkJsonObjectKeysActualInExpected(prefix, expected, actual, result);
32+
}
33+
}
34+
35+
@Override
36+
public void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException {
37+
if (expectedValue.getClass().isAssignableFrom(actualValue.getClass())) {
38+
if (expectedValue instanceof JSONArray) {
39+
compareJSONArray(prefix, (JSONArray) expectedValue, (JSONArray) actualValue, result);
40+
} else if (expectedValue instanceof JSONObject) {
41+
compareJSON(prefix, (JSONObject) expectedValue, (JSONObject) actualValue, result);
42+
} else if (!expectedValue.equals(actualValue)) {
43+
result.fail(prefix, expectedValue, actualValue);
44+
}
45+
} else {
46+
result.fail(prefix, expectedValue, actualValue);
47+
}
48+
}
49+
50+
@Override
51+
public void compareJSONArray(String prefix, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException {
52+
if (expected.length() != actual.length()) {
53+
result.fail(prefix + "[]: Expected " + expected.length() + " values but got " + actual.length());
54+
return;
55+
} else if (expected.length() == 0) {
56+
return; // Nothing to compare
57+
}
58+
59+
if (mode.hasStrictOrder()) {
60+
compareJSONArrayWithStrictOrder(prefix, expected, actual, result);
61+
} else if (allSimpleValues(expected)) {
62+
compareJSONArrayOfSimpleValues(prefix, expected, actual, result);
63+
} else if (allJSONObjects(expected)) {
64+
compareJSONArrayOfJsonObjects(prefix, expected, actual, result);
65+
} else {
66+
// An expensive last resort
67+
recursivelyCompareJSONArray(prefix, expected, actual, result);
68+
}
69+
}
70+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.