Sorting JSONArray using double as comparison violating contract?

Viewed 101

I'm trying to sort a JSONArray but I'm getting the following error:

 Caused by: java.lang.IllegalArgumentException: Comparison method violates its general contract!
     at java.util.TimSort.mergeHi(TimSort.java:895)
     at java.util.TimSort.mergeAt(TimSort.java:512)
     at java.util.TimSort.mergeForceCollapse(TimSort.java:453)
     at java.util.TimSort.sort(TimSort.java:250)
     at java.util.Arrays.sort(Arrays.java:1523)
     at java.util.Collections.sort(Collections.java:238)

Here is my code:

private JSONArray SortJSONArray(String json) {
        JSONArray sortedJsonArray = new JSONArray();

        try {
            JSONArray jsonArr = new JSONArray(json);

            List<JSONObject> jsonValues = new ArrayList<JSONObject>();
            for (int i = 0; i < jsonArr.length(); i++) {
                jsonValues.add(jsonArr.getJSONObject(i));
            }
            Collections.sort(jsonValues, new Comparator<JSONObject>() {
                private static final String KEY_NAME = "EUR";

                @Override
                public int compare(JSONObject a, JSONObject b) {
                    Double valA = 0.0;
                    Double valB = 0.0;

                    try {
                        valA = a.getDouble(KEY_NAME);
                        valB = b.getDouble(KEY_NAME);
                    } catch (JSONException e) {
                        Log.e("MainActivity", e.getMessage());
                    }

                    if(valA < valB) {
                        return -1;
                    } else if( valB < valA) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
            });

            for (int i = 0; i < jsonArr.length(); i++) {
                sortedJsonArray.put(jsonValues.get(i));
            }
        } catch (JSONException e) {
            Log.e("MainActivity", e.getMessage());
        }
        return sortedJsonArray;
    }
2 Answers

The issue is that either a.getDouble(KEY_NAME) or b.getDouble(KEY_NAME) can throw an exception.

If a.getDouble(KEY_NAME) throws an exception and b.getDouble(KEY_NAME) does not, b.getDouble(KEY_NAME) is never evaluated and both valA and valB remain 0.0.

On the other hand, if you compare a and b is reversed order (i.e. call compare(b,a) instead of compare(a,b), b.getDouble(KEY_NAME) will be evaluated before a.getDouble(KEY_NAME) throws an exception, so valA will be 0.0 and valB will have some other value.

In that case compare(a,b) will return 0 and compare(b,a) will return a non-zero value (assuming b.getDouble(KEY_NAME) doesn't return 0.0), which violates the contract of Comparator.

Evaluating a.getDouble(KEY_NAME) and b.getDouble(KEY_NAME) in separate try blocks will resolve the issue.

                try {
                    valA = a.getDouble(KEY_NAME);
                } catch (JSONException e) {
                    Log.e("MainActivity", e.getMessage());
                }
                try {
                    valB = b.getDouble(KEY_NAME);
                } catch (JSONException e) {
                    Log.e("MainActivity", e.getMessage());
                }

                return Double.compare(valA,valB);

Workaround is to return a value in the catch statement:

try {
    valA = a.getDouble(KEY_NAME);
        valB = b.getDouble(KEY_NAME);
    } catch (JSONException e) {
        Log.e("MainActivity", e.getMessage());
        return -10;
    }

    if(valA < valB) {
        return -1;
    } else if( valB < valA) {
        return 1;
    } else {
        return 0;
    }
Related