diff --git a/src/main/java/org/skyscreamer/jsonassert/JSONKeyAssert.java b/src/main/java/org/skyscreamer/jsonassert/JSONKeyAssert.java new file mode 100644 index 00000000..c44319c3 --- /dev/null +++ b/src/main/java/org/skyscreamer/jsonassert/JSONKeyAssert.java @@ -0,0 +1,100 @@ +package org.skyscreamer.jsonassert; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.skyscreamer.jsonassert.comparator.JSONComparator; + +import java.util.Iterator; + +/** + * Class SpecialCompareChange + * Use to handle the comparation ignore the data compare + * + * @author Maijie Xie + * @version 1.0 + * Created on 8/5/2021 + */ +public class JSONKeyAssert { + private JSONKeyAssert() { + } + /** + * Asserts that the JSONArray provided matches the expected string. If it isn't it throws an + * {@link AssertionError}. + * + * @param expectedStr Expected JSON string + * @param actualStr String to compare + * @param strict Enables strict checking + * @throws JSONException JSON parsing error + */ + public static void assertEquals(String expectedStr, String actualStr, boolean strict) + throws JSONException { + expectedStr=removeValue(JSONParser.parseJSON(expectedStr)).toString(); + actualStr =removeValue(JSONParser.parseJSON(actualStr)).toString(); + JSONAssert.assertEquals("", expectedStr, actualStr, strict ? JSONCompareMode.STRICT : JSONCompareMode.LENIENT); + } + /** + * Asserts that the JSONArray provided matches the expected string. If it isn't it throws an + * {@link AssertionError}. + * + * @param expectedStr Expected JSON string + * @param actualStr String to compare + * @param strict Enables strict checking + * @throws JSONException JSON parsing error + */ + public static void assertNotEquals(String expectedStr, String actualStr, boolean strict) + throws JSONException { + expectedStr= removeValue(JSONParser.parseJSON(expectedStr)).toString(); + actualStr =removeValue(JSONParser.parseJSON(actualStr)).toString(); + JSONAssert.assertNotEquals(expectedStr, actualStr, strict ? JSONCompareMode.STRICT : JSONCompareMode.LENIENT); + } + /** + * Remove JSONObject's data + * @param item Expected JSONObject + * @throws JSONException JSON parsing error + */ + private static JSONObject removeValue(JSONObject item) throws JSONException { + JSONObject Temp=new JSONObject(); + Iterator iterator = item.keys(); + while (iterator.hasNext()) { + String key = (String) iterator.next(); + Temp.put(key, 0); + } + return Temp; + + } + /** + * Remove Object's data + * @param item Expected Object + * @throws JSONException JSON parsing error + */ + private static Object removeValue(Object item) throws JSONException{ + if(item instanceof JSONArray){ + return removeValue((JSONArray) item); + } + if(item instanceof JSONObject){ + return removeValue((JSONObject)item); + } + return null; + + } + + /** + * Remove JSONArray's data + * @param item Expected JSONArray + * @throws JSONException JSON parsing error + */ + private static JSONArray removeValue(JSONArray item) throws JSONException { + if (item.length() > 0) { + for (int i = 0; i < item.length(); i++) { + JSONObject job = item.getJSONObject(i); + Iterator iterator = job.keys(); + while (iterator.hasNext()) { + String key = (String) iterator.next(); + job.put(key, 0); + } + } + } + return item; + } +} diff --git a/src/main/java/org/skyscreamer/jsonassert/comparator/AbstractComparator.java b/src/main/java/org/skyscreamer/jsonassert/comparator/AbstractComparator.java index 4f68e2d1..0c88b5f7 100644 --- a/src/main/java/org/skyscreamer/jsonassert/comparator/AbstractComparator.java +++ b/src/main/java/org/skyscreamer/jsonassert/comparator/AbstractComparator.java @@ -10,7 +10,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -*/ + */ package org.skyscreamer.jsonassert.comparator; @@ -78,7 +78,14 @@ protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject exp } } } - + /** + * Compares JSONArray provided to the expected JSONArray, and returns the results of the comparison. + * @param key unique key for the actual JSONArray + * @param expected Expected JSONArray + * @param actual JSONArray to compare + * @throws JSONException JSON parsing error + */ + @SuppressWarnings("checkstyle:WhitespaceAround") protected void compareJSONArrayOfJsonObjects(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException { String uniqueKey = findUniqueKey(expected); if (uniqueKey == null || !isUsableAsUniqueKey(uniqueKey, actual)) { @@ -89,7 +96,7 @@ protected void compareJSONArrayOfJsonObjects(String key, JSONArray expected, JSO Map expectedValueMap = arrayOfJsonObjectToMap(expected, uniqueKey); Map actualValueMap = arrayOfJsonObjectToMap(actual, uniqueKey); for (Object id : expectedValueMap.keySet()) { - if (!actualValueMap.containsKey(id)) { + if ((!actualValueMap.containsKey(id))&&(!actualValueMap.containsKey(Double.parseDouble(id.toString())))) { result.missing(formatUniqueKey(key, uniqueKey, id), expectedValueMap.get(id)); continue; } diff --git a/src/main/java/org/skyscreamer/jsonassert/comparator/JSONCompareUtil.java b/src/main/java/org/skyscreamer/jsonassert/comparator/JSONCompareUtil.java index a6bcc4ce..10b28c2d 100644 --- a/src/main/java/org/skyscreamer/jsonassert/comparator/JSONCompareUtil.java +++ b/src/main/java/org/skyscreamer/jsonassert/comparator/JSONCompareUtil.java @@ -10,233 +10,171 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -*/ + */ package org.skyscreamer.jsonassert.comparator; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.skyscreamer.jsonassert.JSONCompareResult; + +import java.util.*; + +import static org.skyscreamer.jsonassert.comparator.JSONCompareUtil.*; /** - * Utility class that contains Json manipulation methods. + * This class provides a skeletal implementation of the {@link JSONComparator} + * interface, to minimize the effort required to implement this interface. */ -public final class JSONCompareUtil { - private static Integer INTEGER_ONE = new Integer(1); - - private JSONCompareUtil() { - } +public abstract class AbstractComparator implements JSONComparator { /** - * Converts the provided {@link JSONArray} to a Map of {@link JSONObject}s where the key of each object - * is the value at {@code uniqueKey} in each object. + * Compares JSONObject provided to the expected JSONObject, and returns the results of the comparison. * - * @param array the JSON array to convert - * @param uniqueKey the key to map the JSON objects to - * @return the map of {@link JSONObject}s from {@code array} + * @param expected Expected JSONObject + * @param actual JSONObject to compare * @throws JSONException JSON parsing error */ - public static Map arrayOfJsonObjectToMap(JSONArray array, String uniqueKey) throws JSONException { - Map valueMap = new HashMap(); - for (int i = 0; i < array.length(); ++i) { - JSONObject jsonObject = (JSONObject) array.get(i); - Object id = jsonObject.get(uniqueKey); - valueMap.put(id, jsonObject); - } - return valueMap; + @Override + public final JSONCompareResult compareJSON(JSONObject expected, JSONObject actual) throws JSONException { + JSONCompareResult result = new JSONCompareResult(); + compareJSON("", expected, actual, result); + return result; } /** - * Searches for the unique key of the {@code expected} JSON array. + * Compares JSONArray provided to the expected JSONArray, and returns the results of the comparison. * - * @param expected the array to find the unique key of - * @return the unique key if there's any, otherwise null + * @param expected Expected JSONArray + * @param actual JSONArray to compare * @throws JSONException JSON parsing error */ - public static String findUniqueKey(JSONArray expected) throws JSONException { - // Find a unique key for the object (id, name, whatever) - JSONObject o = (JSONObject) expected.get(0); // There's at least one at this point - for (String candidate : getKeys(o)) { - if (isUsableAsUniqueKey(candidate, expected)) return candidate; - } - // No usable unique key :-( - return null; + @Override + public final JSONCompareResult compareJSON(JSONArray expected, JSONArray actual) throws JSONException { + JSONCompareResult result = new JSONCompareResult(); + compareJSONArray("", expected, actual, result); + return result; } - /** - *

Looks to see if candidate field is a possible unique key across a array of objects. - * Returns true IFF:

- *
    - *
  1. array is an array of JSONObject - *
  2. candidate is a top-level field in each of of the objects in the array - *
  3. candidate is a simple value (not JSONObject or JSONArray) - *
  4. candidate is unique across all elements in the array - *
- * - * @param candidate is usable as a unique key if every element in the - * @param array is a JSONObject having that key, and no two values are the same. - * @return true if the candidate can work as a unique id across array - * @throws JSONException JSON parsing error - */ - public static boolean isUsableAsUniqueKey(String candidate, JSONArray array) throws JSONException { - Set seenValues = new HashSet(); - for (int i = 0; i < array.length(); i++) { - Object item = array.get(i); - if (item instanceof JSONObject) { - JSONObject o = (JSONObject) item; - if (o.has(candidate)) { - Object value = o.get(candidate); - if (isSimpleValue(value) && !seenValues.contains(value)) { - seenValues.add(value); - } else { - return false; - } - } else { - return false; - } - } else { - return false; + protected void checkJsonObjectKeysActualInExpected(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) { + Set actualKeys = getKeys(actual); + for (String key : actualKeys) { + if (!expected.has(key)) { + result.unexpected(prefix, key); } } - return true; } - /** - * Converts the given {@link JSONArray} to a list of {@link Object}s. - * - * @param expected the JSON array to convert - * @return the list of objects from the {@code expected} array - * @throws JSONException JSON parsing error - */ - public static List jsonArrayToList(JSONArray expected) throws JSONException { - List jsonObjects = new ArrayList(expected.length()); - for (int i = 0; i < expected.length(); ++i) { - jsonObjects.add(getObjectOrNull(expected, i)); + protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException { + Set expectedKeys = getKeys(expected); + for (String key : expectedKeys) { + Object expectedValue = expected.get(key); + if (actual.has(key)) { + Object actualValue = actual.get(key); + compareValues(qualify(prefix, key), expectedValue, actualValue, result); + } else { + result.missing(prefix, key); + } } - return jsonObjects; - } - - /** - * Returns the value present in the given index position. If null value is present, it will return null - * - * @param jsonArray the JSON array to get value from - * @param index index of object to retrieve - * @return value at the given index position - * @throws JSONException JSON parsing error - */ - public static Object getObjectOrNull(JSONArray jsonArray, int index) throws JSONException { - return jsonArray.isNull(index) ? null : jsonArray.get(index); } - /** - * Returns whether all of the elements in the given array are simple values. - * - * @param array the JSON array to iterate through on - * @return true if all the elements in {@code array} are simple values - * @throws JSONException JSON parsing error - * @see #isSimpleValue(Object) - */ - public static boolean allSimpleValues(JSONArray array) throws JSONException { - for (int i = 0; i < array.length(); ++i) { - if (!array.isNull(i) && !isSimpleValue(array.get(i))) { - return false; + protected void compareJSONArrayOfJsonObjects(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException { + String uniqueKey = findUniqueKey(expected); + if (uniqueKey == null || !isUsableAsUniqueKey(uniqueKey, actual)) { + // An expensive last resort + recursivelyCompareJSONArray(key, expected, actual, result); + return; + } + Map expectedValueMap = arrayOfJsonObjectToMap(expected, uniqueKey); + Map actualValueMap = arrayOfJsonObjectToMap(actual, uniqueKey); + for (Object id : expectedValueMap.keySet()) { + if ((!actualValueMap.containsKey(id))&&(!actualValueMap.containsKey(Double.parseDouble(id.toString())))) { + result.missing(formatUniqueKey(key, uniqueKey, id), expectedValueMap.get(id)); + continue; } + JSONObject expectedValue = expectedValueMap.get(id); + JSONObject actualValue = actualValueMap.get(id); + compareValues(formatUniqueKey(key, uniqueKey, id), expectedValue, actualValue, result); } - return true; - } - - /** - * Returns whether the given object is a simple value: not {@link JSONObject} and not {@link JSONArray}. - * - * @param o the object to inspect - * @return true if {@code o} is a simple value - */ - public static boolean isSimpleValue(Object o) { - return !(o instanceof JSONObject) && !(o instanceof JSONArray); - } - - /** - * Returns whether all elements in {@code array} are {@link JSONObject} instances. - * - * @param array the array to inspect - * @return true if all the elements in the given array are JSONObjects - * @throws JSONException JSON parsing error - */ - public static boolean allJSONObjects(JSONArray array) throws JSONException { - for (int i = 0; i < array.length(); ++i) { - if (!(array.get(i) instanceof JSONObject)) { - return false; + for (Object id : actualValueMap.keySet()) { + if (!expectedValueMap.containsKey(id)) { + result.unexpected(formatUniqueKey(key, uniqueKey, id), actualValueMap.get(id)); } } - return true; } - /** - * Returns whether all elements in {@code array} are {@link JSONArray} instances. - * - * @param array the array to inspect - * @return true if all the elements in the given array are JSONArrays - * @throws JSONException JSON parsing error - */ - public static boolean allJSONArrays(JSONArray array) throws JSONException { - for (int i = 0; i < array.length(); ++i) { - if (!(array.get(i) instanceof JSONArray)) { - return false; + protected void compareJSONArrayOfSimpleValues(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException { + Map expectedCount = JSONCompareUtil.getCardinalityMap(jsonArrayToList(expected)); + Map actualCount = JSONCompareUtil.getCardinalityMap(jsonArrayToList(actual)); + for (Object o : expectedCount.keySet()) { + if (!actualCount.containsKey(o)) { + result.missing(key + "[]", o); + } else if (!actualCount.get(o).equals(expectedCount.get(o))) { + result.fail(key + "[]: Expected " + expectedCount.get(o) + " occurrence(s) of " + o + + " but got " + actualCount.get(o) + " occurrence(s)"); } } - return true; - } - - /** - * Collects all keys in {@code jsonObject}. - * - * @param jsonObject the {@link JSONObject} to get the keys of - * @return the set of keys - */ - public static Set getKeys(JSONObject jsonObject) { - Set keys = new TreeSet(); - Iterator iter = jsonObject.keys(); - while (iter.hasNext()) { - keys.add((String) iter.next()); + for (Object o : actualCount.keySet()) { + if (!expectedCount.containsKey(o)) { + result.unexpected(key + "[]", o); + } } - return keys; } - public static String qualify(String prefix, String key) { - return "".equals(prefix) ? key : prefix + "." + key; - } - - public static String formatUniqueKey(String key, String uniqueKey, Object value) { - return key + "[" + uniqueKey + "=" + value + "]"; + protected void compareJSONArrayWithStrictOrder(String key, JSONArray expected, JSONArray actual, JSONCompareResult result) throws JSONException { + for (int i = 0; i < expected.length(); ++i) { + Object expectedValue = JSONCompareUtil.getObjectOrNull(expected, i); + Object actualValue = JSONCompareUtil.getObjectOrNull(actual, i); + compareValues(key + "[" + i + "]", expectedValue, actualValue, result); + } } - /** - * Creates a cardinality map from {@code coll}. - * - * @param coll the collection of items to convert - * @param the type of elements in the input collection - * @return the cardinality map - */ - public static Map getCardinalityMap(final Collection coll) { - Map count = new HashMap(); - for (T item : coll) { - Integer c = (Integer) (count.get(item)); - if (c == null) { - count.put(item, INTEGER_ONE); - } else { - count.put(item, new Integer(c.intValue() + 1)); + // This is expensive (O(n^2) -- yuck), but may be the only resort for some cases with loose array ordering, and no + // easy way to uniquely identify each element. + // This is expensive (O(n^2) -- yuck), but may be the only resort for some cases with loose array ordering, and no + // easy way to uniquely identify each element. + protected void recursivelyCompareJSONArray(String key, JSONArray expected, JSONArray actual, + JSONCompareResult result) throws JSONException { + Set matched = new HashSet(); + for (int i = 0; i < expected.length(); ++i) { + Object expectedElement = JSONCompareUtil.getObjectOrNull(expected, i); + boolean matchFound = false; + for (int j = 0; j < actual.length(); ++j) { + Object actualElement = JSONCompareUtil.getObjectOrNull(actual, j); + if (expectedElement == actualElement) { + matchFound = true; + break; + } + if ((expectedElement == null && actualElement != null) || (expectedElement != null && actualElement == null)) { + continue; + } + if (matched.contains(j) || !actualElement.getClass().equals(expectedElement.getClass())) { + continue; + } + if (expectedElement instanceof JSONObject) { + if (compareJSON((JSONObject) expectedElement, (JSONObject) actualElement).passed()) { + matched.add(j); + matchFound = true; + break; + } + } else if (expectedElement instanceof JSONArray) { + if (compareJSON((JSONArray) expectedElement, (JSONArray) actualElement).passed()) { + matched.add(j); + matchFound = true; + break; + } + } else if (expectedElement.equals(actualElement)) { + matched.add(j); + matchFound = true; + break; + } + } + if (!matchFound) { + result.fail(key + "[" + i + "] Could not find match for element " + expectedElement); + return; } } - return count; } } + diff --git a/src/test/java/org/skyscreamer/jsonassert/JSONElementTest.java b/src/test/java/org/skyscreamer/jsonassert/JSONElementTest.java new file mode 100644 index 00000000..878c0746 --- /dev/null +++ b/src/test/java/org/skyscreamer/jsonassert/JSONElementTest.java @@ -0,0 +1,36 @@ +package org.skyscreamer.jsonassert; + + import org.json.JSONException; + import org.junit.Test; + +public class JSONElementTest +{ + // This test fails + @Test + public void testArrayCompareDifElement() throws JSONException { + String actual = "[{ \"id\" : \"123\", \"courseID\" : \"test\"},{ \"id\" : \"124\", \"courseID\" : \"te\"}] "; + String expected = "[{ \"ID\" : \"127\", \"courseID\" : \"te\"},{ \"id\" : \"125\", \"courseID\" : \"ted\"}] "; + JSONKeyAssert.assertNotEquals(expected, actual, false); + } + + @Test + public void testArrayCompareSameElement() throws JSONException { + String actual = "[{ \"id\" : \"123\", \"courseID\" : \"test\"},{ \"id\" : \"124\", \"courseID\" : \"te\"}] "; + String expected = "[{ \"id\" : \"127\", \"courseID\" : \"te\"},{ \"id\" : \"125\", \"courseID\" : \"ted\"}] "; + JSONKeyAssert.assertEquals(expected, actual, false); + } + + @Test + public void testObjectCompareSameElement() throws JSONException { + String actual = "{ \"id\" : \"123\", \"courseID\" : \"test\", \"title\" : \"ask question\", \"content\" : null } "; + String expected = "{ \"id\" : \"1\", \"courseID\" : \"test\", \"title\" : \"answer question\", \"content\" : null } "; + JSONKeyAssert.assertEquals(expected, actual, true); + } + + @Test + public void testObjectCompareDifElement() throws JSONException { + String actual = "{ \"id\" : \"123\", \"courseID\" : \"test\", \"title\" : \"ask question\", \"content\" : null } "; + String expected = "{ \"ID\" : \"1\", \"courseID\" : \"test\", \"title\" : \"answer question\", \"content\" : null } "; + JSONKeyAssert.assertNotEquals(expected, actual, true); + } +} \ No newline at end of file diff --git a/src/test/java/org/skyscreamer/jsonassert/JSONPrecisionTest.java b/src/test/java/org/skyscreamer/jsonassert/JSONPrecisionTest.java new file mode 100644 index 00000000..13c5ac50 --- /dev/null +++ b/src/test/java/org/skyscreamer/jsonassert/JSONPrecisionTest.java @@ -0,0 +1,36 @@ +package org.skyscreamer.jsonassert; + +import org.json.JSONException; +import org.junit.Test; + +public class JSONPrecisionTest +{ + //CS304 (manually written) Issue link: https://github.com/skyscreamer/JSONassert/issues/115 + @Test + public void testArrayCompareDifferingPrecision() throws JSONException { + String actual = "[{ \"Foo\" : 1.0000 }]"; + String expected = "[{ \"Foo\" : 1 }]"; + JSONAssert.assertEquals(expected, actual, false); + } + //CS304 (manually written) Issue link: https://github.com/skyscreamer/JSONassert/issues/115 + @Test + public void testArrayCompareSamePrecision() throws JSONException { + String actual = "[{ \"Foo\" : 1.0 }]"; + String expected = "[{ \"Foo\" : 1.0 }]"; + JSONAssert.assertEquals(expected, actual, false); + } + //CS304 (manually written) Issue link: https://github.com/skyscreamer/JSONassert/issues/115 + @Test + public void testObjectCompareDifferingPrecision() throws JSONException { + String actual = "{ \"Foo\" : 1.00 }"; + String expected = "{ \"Foo\" : 1 }"; + JSONAssert.assertEquals(expected, actual, true); + } + //CS304 (manually written) Issue link: https://github.com/skyscreamer/JSONassert/issues/115 + @Test + public void testObjectCompareSamePrecision() throws JSONException { + String actual = "{ \"Foo\" : 1.00 }"; + String expected = "{ \"Foo\" : 1 }"; + JSONAssert.assertEquals(expected, actual, true); + } +}