Merge pull request #617 from johnjaylward/issue-616-similar-bug

Fixes #616 similar() problem comparing double vs BigDecimal
This commit is contained in:
Sean Leary 2021-07-26 17:47:03 -05:00 committed by GitHub
commit bb048e3ffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 40 additions and 21 deletions

View File

@ -1159,6 +1159,18 @@ public class JSONObject {
* to convert. * to convert.
*/ */
static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) { static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
return objectToBigDecimal(val, defaultValue, true);
}
/**
* @param val value to convert
* @param defaultValue default value to return is the conversion doesn't work or is null.
* @param exact When <code>true</code>, then {@link Double} and {@link Float} values will be converted exactly.
* When <code>false</code>, they will be converted to {@link String} values before converting to {@link BigDecimal}.
* @return BigDecimal conversion of the original value, or the defaultValue if unable
* to convert.
*/
static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue, boolean exact) {
if (NULL.equals(val)) { if (NULL.equals(val)) {
return defaultValue; return defaultValue;
} }
@ -1172,7 +1184,14 @@ public class JSONObject {
if (!numberIsFinite((Number)val)) { if (!numberIsFinite((Number)val)) {
return defaultValue; return defaultValue;
} }
if (exact) {
return new BigDecimal(((Number)val).doubleValue()); return new BigDecimal(((Number)val).doubleValue());
}else {
// use the string constructor so that we maintain "nice" values for doubles and floats
// the double constructor will translate doubles to "exact" values instead of the likely
// intended representation
return new BigDecimal(val.toString());
}
} }
if (val instanceof Long || val instanceof Integer if (val instanceof Long || val instanceof Integer
|| val instanceof Short || val instanceof Byte){ || val instanceof Short || val instanceof Byte){
@ -1639,9 +1658,6 @@ public class JSONObject {
* implementations and interfaces has the annotation. Returns the depth of the * implementations and interfaces has the annotation. Returns the depth of the
* annotation in the hierarchy. * annotation in the hierarchy.
* *
* @param <A>
* type of the annotation
*
* @param m * @param m
* method to check * method to check
* @param annotationClass * @param annotationClass
@ -2135,8 +2151,8 @@ public class JSONObject {
// BigDecimal should be able to handle all of our number types that we support through // 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 // documentation. Convert to BigDecimal first, then use the Compare method to
// decide equality. // decide equality.
final BigDecimal lBigDecimal = objectToBigDecimal(l, null); final BigDecimal lBigDecimal = objectToBigDecimal(l, null, false);
final BigDecimal rBigDecimal = objectToBigDecimal(r, null); final BigDecimal rBigDecimal = objectToBigDecimal(r, null, false);
if (lBigDecimal == null || rBigDecimal == null) { if (lBigDecimal == null || rBigDecimal == null) {
return false; return false;
} }

View File

@ -188,7 +188,7 @@ public class JSONPointer {
} }
/** /**
* @see https://tools.ietf.org/html/rfc6901#section-3 * @see <a href="https://tools.ietf.org/html/rfc6901#section-3">rfc6901 section 3</a>
*/ */
private static String unescape(String token) { private static String unescape(String token) {
return token.replace("~1", "/").replace("~0", "~"); return token.replace("~1", "/").replace("~0", "~");
@ -268,7 +268,7 @@ public class JSONPointer {
* @param token the JSONPointer segment value to be escaped * @param token the JSONPointer segment value to be escaped
* @return the escaped value for the token * @return the escaped value for the token
* *
* @see https://tools.ietf.org/html/rfc6901#section-3 * @see <a href="https://tools.ietf.org/html/rfc6901#section-3">rfc6901 section 3</a>
*/ */
private static String escape(String token) { private static String escape(String token) {
return token.replace("~", "~0") return token.replace("~", "~0")

View File

@ -182,7 +182,7 @@ public class XMLParserConfiguration {
* When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if * When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string) * they should try to be guessed into JSON values (numeric, boolean, string)
* *
* @return The {@link #keepStrings} configuration value. * @return The <code>keepStrings</code> configuration value.
*/ */
public boolean isKeepStrings() { public boolean isKeepStrings() {
return this.keepStrings; return this.keepStrings;
@ -193,7 +193,7 @@ public class XMLParserConfiguration {
* they should try to be guessed into JSON values (numeric, boolean, string) * they should try to be guessed into JSON values (numeric, boolean, string)
* *
* @param newVal * @param newVal
* new value to use for the {@link #keepStrings} configuration option. * new value to use for the <code>keepStrings</code> configuration option.
* *
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
@ -208,7 +208,7 @@ public class XMLParserConfiguration {
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
* processing. * processing.
* *
* @return The {@link #cDataTagName} configuration value. * @return The <code>cDataTagName</code> configuration value.
*/ */
public String getcDataTagName() { public String getcDataTagName() {
return this.cDataTagName; return this.cDataTagName;
@ -220,7 +220,7 @@ public class XMLParserConfiguration {
* processing. * processing.
* *
* @param newVal * @param newVal
* new value to use for the {@link #cDataTagName} configuration option. * new value to use for the <code>cDataTagName</code> configuration option.
* *
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
@ -235,7 +235,7 @@ public class XMLParserConfiguration {
* should be kept as attribute(<code>false</code>), or they should be converted to * should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>) * <code>null</code>(<code>true</code>)
* *
* @return The {@link #convertNilAttributeToNull} configuration value. * @return The <code>convertNilAttributeToNull</code> configuration value.
*/ */
public boolean isConvertNilAttributeToNull() { public boolean isConvertNilAttributeToNull() {
return this.convertNilAttributeToNull; return this.convertNilAttributeToNull;
@ -247,7 +247,7 @@ public class XMLParserConfiguration {
* <code>null</code>(<code>true</code>) * <code>null</code>(<code>true</code>)
* *
* @param newVal * @param newVal
* new value to use for the {@link #convertNilAttributeToNull} configuration option. * new value to use for the <code>convertNilAttributeToNull</code> configuration option.
* *
* @return The existing configuration will not be modified. A new configuration is returned. * @return The existing configuration will not be modified. A new configuration is returned.
*/ */
@ -262,7 +262,7 @@ public class XMLParserConfiguration {
* will be converted to target type defined to client in this configuration * will be converted to target type defined to client in this configuration
* {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute * {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string * xsi:type="integer" as integer, xsi:type="string" as string
* @return {@link #xsiTypeMap} unmodifiable configuration map. * @return <code>xsiTypeMap</code> unmodifiable configuration map.
*/ */
public Map<String, XMLXsiTypeConverter<?>> getXsiTypeMap() { public Map<String, XMLXsiTypeConverter<?>> getXsiTypeMap() {
return this.xsiTypeMap; return this.xsiTypeMap;

View File

@ -126,6 +126,9 @@ public class JSONObjectTest {
assertTrue("Should eval to true", obj1.similar(obj4)); assertTrue("Should eval to true", obj1.similar(obj4));
// verify that a double and big decimal are "similar"
assertTrue("should eval to true",new JSONObject().put("a",1.1d).similar(new JSONObject("{\"a\":1.1}")));
} }
@Test @Test
@ -940,7 +943,7 @@ public class JSONObjectTest {
assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double); assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double);
assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double); assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double);
assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String); assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String);
assertTrue( "0.2 should be a Double!", assertTrue( "0.2 should be a BigDecimal!",
JSONObject.stringToValue( "0.2" ) instanceof BigDecimal ); JSONObject.stringToValue( "0.2" ) instanceof BigDecimal );
assertTrue( "Doubles should be BigDecimal, even when incorrectly converting floats!", assertTrue( "Doubles should be BigDecimal, even when incorrectly converting floats!",
JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof BigDecimal ); JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof BigDecimal );

View File

@ -120,7 +120,7 @@ public class JSONPointerTest {
/** /**
* We pass backslashes as-is * We pass backslashes as-is
* *
* @see https://tools.ietf.org/html/rfc6901#section-3 * @see <a href="https://tools.ietf.org/html/rfc6901#section-3">rfc6901 section 3</a>
*/ */
@Test @Test
public void backslashHandling() { public void backslashHandling() {
@ -130,7 +130,7 @@ public class JSONPointerTest {
/** /**
* We pass quotations as-is * We pass quotations as-is
* *
* @see https://tools.ietf.org/html/rfc6901#section-3 * @see <a href="https://tools.ietf.org/html/rfc6901#section-3">rfc6901 section 3</a>
*/ */
@Test @Test
public void quotationHandling() { public void quotationHandling() {

View File

@ -8,7 +8,7 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
/** /**
* Object for testing the exception handling in {@link JSONObject#populateMap}. * Object for testing the exception handling in {@link org.json.JSONObject#populateMap}.
* *
* @author John Aylward * @author John Aylward
*/ */