From dec2b8bbe2c0c1ff4f39b22d0238fb050458b0ba Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Thu, 19 Nov 2020 18:24:56 -0500 Subject: [PATCH] fixes issue #573 by added specific compare of numeric types --- src/main/java/org/json/JSONArray.java | 2 ++ src/main/java/org/json/JSONObject.java | 49 +++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index f11b328d8..338177c59 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -1374,6 +1374,8 @@ public boolean similar(Object other) { if (!((JSONArray)valueThis).similar(valueOther)) { return false; } + } else if (valueThis instanceof Number && valueOther instanceof Number) { + return JSONObject.isNumberSimilar((Number)valueThis, (Number)valueOther); } else if (!valueThis.equals(valueOther)) { return false; } diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index f718c0618..2d870b0c8 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -2073,6 +2073,8 @@ public boolean similar(Object other) { if (!((JSONArray)valueThis).similar(valueOther)) { return false; } + } else if (valueThis instanceof Number && valueOther instanceof Number) { + return isNumberSimilar((Number)valueThis, (Number)valueOther); } else if (!valueThis.equals(valueOther)) { return false; } @@ -2083,6 +2085,51 @@ public boolean similar(Object other) { } } + /** + * Compares two numbers to see if they are similar. + * + * If either of the numbers are Double or Float instances, then they are checked to have + * a finite value. If either value is not finite (NaN or ±infinity), then this + * function will always return false. If both numbers are finite, they are first checked + * to be the same type and implement {@link Comparable}. If they do, then the actual + * {@link Comparable#compareTo(Object)} is called. If they are not the same type, or don't + * implement Comparable, then they are converted to {@link String}s, then to + * {@link BigDecimal}s. Finally the 2 BigDecimal values are compared using + * {@link BigDecimal#compareTo(BigDecimal)}. + * + * @param l the Left value to compare. Can not be null. + * @param r the right value to compare. Can not be null. + * @return true if the numbers are similar, false otherwise. + */ + static boolean isNumberSimilar(Number l, Number r) { + if (!numberIsFinite(l) || !numberIsFinite(r)) { + // non-finite numbers are never similar + return false; + } + + // if the classes are the same and implement Comparable + // then use the built in compare first. + if(l.getClass().equals(r.getClass()) && l instanceof Comparable) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + int compareTo = ((Comparable)l).compareTo(r); + return compareTo==0; + } + + // BigDecimal should be able to handle all of our number types that we support through + // documentation. Convert to BigDecimal first, then use the Compare method to + // decide equality. + return new BigDecimal(l.toString()).compareTo(new BigDecimal(r.toString())) == 0; + } + + private static boolean numberIsFinite(Number n) { + if (n instanceof Double && (((Double) n).isInfinite() || ((Double) n).isNaN())) { + return false; + } else if (n instanceof Float && (((Float) n).isInfinite() || ((Float) n).isNaN())) { + return false; + } + return true; + } + /** * Tests if the value should be tried as a decimal. It makes no test if there are actual digits. * @@ -2354,7 +2401,7 @@ public static String valueToString(Object value) throws JSONException { */ public static Object wrap(Object object) { try { - if (object == null) { + if (NULL.equals(object)) { return NULL; } if (object instanceof JSONObject || object instanceof JSONArray