Skip to content

Commit

Permalink
Merge pull request #617 from johnjaylward/issue-616-similar-bug
Browse files Browse the repository at this point in the history
Fixes #616 similar() problem comparing double vs BigDecimal
  • Loading branch information
stleary committed Jul 26, 2021
2 parents 449ec87 + 579784d commit bb048e3
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 21 deletions.
28 changes: 22 additions & 6 deletions src/main/java/org/json/JSONObject.java
Expand Up @@ -1159,6 +1159,18 @@ public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) {
* to convert.
*/
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)) {
return defaultValue;
}
Expand All @@ -1172,7 +1184,14 @@ static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
if (!numberIsFinite((Number)val)) {
return defaultValue;
}
return new BigDecimal(((Number) val).doubleValue());
if (exact) {
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
|| val instanceof Short || val instanceof Byte){
Expand Down Expand Up @@ -1639,9 +1658,6 @@ private static <A extends Annotation> A getAnnotation(final Method m, final Clas
* implementations and interfaces has the annotation. Returns the depth of the
* annotation in the hierarchy.
*
* @param <A>
* type of the annotation
*
* @param m
* method to check
* @param annotationClass
Expand Down Expand Up @@ -2135,8 +2151,8 @@ static boolean isNumberSimilar(Number l, Number r) {
// 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.
final BigDecimal lBigDecimal = objectToBigDecimal(l, null);
final BigDecimal rBigDecimal = objectToBigDecimal(r, null);
final BigDecimal lBigDecimal = objectToBigDecimal(l, null, false);
final BigDecimal rBigDecimal = objectToBigDecimal(r, null, false);
if (lBigDecimal == null || rBigDecimal == null) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/json/JSONPointer.java
Expand Up @@ -188,7 +188,7 @@ public JSONPointer(List<String> refTokens) {
}

/**
* @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) {
return token.replace("~1", "/").replace("~0", "~");
Expand Down Expand Up @@ -268,7 +268,7 @@ public String toString() {
* @param token the JSONPointer segment value to be escaped
* @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) {
return token.replace("~", "~0")
Expand Down
18 changes: 9 additions & 9 deletions src/main/java/org/json/XMLParserConfiguration.java
Expand Up @@ -94,7 +94,7 @@ public XMLParserConfiguration (final boolean keepStrings) {
* Configure the parser string processing to try and convert XML values to JSON values and
* use the passed CDATA Tag Name the processing value. Pass <code>null</code> to
* disable CDATA processing
* @param cDataTagName<code>null</code> to disable CDATA processing. Any other value
* @param cDataTagName <code>null</code> to disable CDATA processing. Any other value
* to use that value as the JSONObject key name to process as CDATA.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
Expand All @@ -109,7 +109,7 @@ public XMLParserConfiguration (final String cDataTagName) {
* Configure the parser to use custom settings.
* @param keepStrings <code>true</code> to parse all values as string.
* <code>false</code> to try and convert XML string values into a JSON value.
* @param cDataTagName<code>null</code> to disable CDATA processing. Any other value
* @param cDataTagName <code>null</code> to disable CDATA processing. Any other value
* to use that value as the JSONObject key name to process as CDATA.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
Expand Down Expand Up @@ -182,7 +182,7 @@ protected XMLParserConfiguration clone() {
* 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)
*
* @return The {@link #keepStrings} configuration value.
* @return The <code>keepStrings</code> configuration value.
*/
public boolean isKeepStrings() {
return this.keepStrings;
Expand All @@ -193,7 +193,7 @@ public boolean isKeepStrings() {
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @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.
*/
Expand All @@ -208,7 +208,7 @@ public XMLParserConfiguration withKeepStrings(final boolean newVal) {
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
* processing.
*
* @return The {@link #cDataTagName} configuration value.
* @return The <code>cDataTagName</code> configuration value.
*/
public String getcDataTagName() {
return this.cDataTagName;
Expand All @@ -220,7 +220,7 @@ public String getcDataTagName() {
* processing.
*
* @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.
*/
Expand All @@ -235,7 +235,7 @@ public XMLParserConfiguration withcDataTagName(final String newVal) {
* should be kept as attribute(<code>false</code>), or they should be converted to
* <code>null</code>(<code>true</code>)
*
* @return The {@link #convertNilAttributeToNull} configuration value.
* @return The <code>convertNilAttributeToNull</code> configuration value.
*/
public boolean isConvertNilAttributeToNull() {
return this.convertNilAttributeToNull;
Expand All @@ -247,7 +247,7 @@ public boolean isConvertNilAttributeToNull() {
* <code>null</code>(<code>true</code>)
*
* @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.
*/
Expand All @@ -262,7 +262,7 @@ public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal
* will be converted to target type defined to client in this configuration
* {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute
* 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() {
return this.xsiTypeMap;
Expand Down
5 changes: 4 additions & 1 deletion src/test/java/org/json/junit/JSONObjectTest.java
Expand Up @@ -126,6 +126,9 @@ public void verifySimilar() {

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
Expand Down Expand Up @@ -940,7 +943,7 @@ public void stringToValueNumbersTest() {
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("'-' 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 );
assertTrue( "Doubles should be BigDecimal, even when incorrectly converting floats!",
JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof BigDecimal );
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/org/json/junit/JSONPointerTest.java
Expand Up @@ -120,7 +120,7 @@ public void tildeEscaping() {
/**
* 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
public void backslashHandling() {
Expand All @@ -130,7 +130,7 @@ public void backslashHandling() {
/**
* 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
public void quotationHandling() {
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/org/json/junit/data/ExceptionalBean.java
Expand Up @@ -8,7 +8,7 @@
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
*/
Expand Down

0 comments on commit bb048e3

Please sign in to comment.