From a63fa0306213e835c21cc7432220394ed2798d2d Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Tue, 2 Oct 2018 12:37:15 -0400 Subject: [PATCH] * Fixes opt/getBigDecimal to be consistent * Performance: Updates JSONWriter to use a regex to decide if writing as a number is best. --- JSONArray.java | 64 +++++++--------------------------------- JSONObject.java | 78 ++++++++++++++++++++++++++++++++++--------------- JSONWriter.java | 12 +++----- 3 files changed, 69 insertions(+), 85 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index fbc1a0f..dd22046 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -345,12 +345,12 @@ public class JSONArray implements Iterable { */ public BigDecimal getBigDecimal (int index) throws JSONException { Object object = this.get(index); - try { - return new BigDecimal(object.toString()); - } catch (Exception e) { + BigDecimal val = JSONObject.objectToBigDecimal(object, null); + if(val == null) { throw new JSONException("JSONArray[" + index + - "] could not convert to BigDecimal.", e); + "] could not convert to BigDecimal ("+ object + ")."); } + return val; } /** @@ -365,12 +365,12 @@ public class JSONArray implements Iterable { */ public BigInteger getBigInteger (int index) throws JSONException { Object object = this.get(index); - try { - return new BigInteger(object.toString()); - } catch (Exception e) { + BigInteger val = JSONObject.objectToBigInteger(object, null); + if(val == null) { throw new JSONException("JSONArray[" + index + - "] could not convert to BigInteger.", e); + "] could not convert to BigDecimal ("+ object + ")."); } + return val; } /** @@ -739,31 +739,7 @@ public class JSONArray implements Iterable { */ public BigInteger optBigInteger(int index, BigInteger defaultValue) { Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigInteger){ - return (BigInteger) val; - } - if (val instanceof BigDecimal){ - return ((BigDecimal) val).toBigInteger(); - } - if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); - } - if (val instanceof Long || val instanceof Integer - || val instanceof Short || val instanceof Byte){ - return BigInteger.valueOf(((Number) val).longValue()); - } - try { - final String valStr = val.toString(); - if(JSONObject.isDecimalNotation(valStr)) { - return new BigDecimal(valStr).toBigInteger(); - } - return new BigInteger(valStr); - } catch (Exception e) { - return defaultValue; - } + return JSONObject.objectToBigInteger(val, defaultValue); } /** @@ -779,27 +755,7 @@ public class JSONArray implements Iterable { */ public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigDecimal){ - return (BigDecimal) val; - } - if (val instanceof BigInteger){ - return new BigDecimal((BigInteger) val); - } - if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()); - } - if (val instanceof Long || val instanceof Integer - || val instanceof Short || val instanceof Byte){ - return new BigDecimal(((Number) val).longValue()); - } - try { - return new BigDecimal(val.toString()); - } catch (Exception e) { - return defaultValue; - } + return JSONObject.objectToBigDecimal(val, defaultValue); } /** diff --git a/JSONObject.java b/JSONObject.java index 8deb6ba..1a9b9de 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -45,6 +45,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.ResourceBundle; import java.util.Set; +import java.util.regex.Pattern; /** * A JSONObject is an unordered collection of name/value pairs. Its external @@ -150,6 +151,12 @@ public class JSONObject { return "null"; } } + + /** + * Regular Expression Pattern that matches JSON Numbers. This is primarily used for + * output to guarantee that we are always writing valid JSON. + */ + static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?"); /** * The map where the JSONObject's properties are kept. @@ -630,16 +637,19 @@ public class JSONObject { */ public BigInteger getBigInteger(String key) throws JSONException { Object object = this.get(key); - try { - return new BigInteger(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] could not be converted to BigInteger.", e); + BigInteger ret = objectToBigInteger(object, null); + if (ret != null) { + return ret; } + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger (" + object + ")."); } /** - * Get the BigDecimal value associated with a key. + * Get the BigDecimal value associated with a key. If the value is float or + * double, the the {@link BigDecimal#BigDecimal(double)} constructor will + * be used. See notes on the constructor for conversion issues that may + * arise. * * @param key * A key string. @@ -650,15 +660,12 @@ public class JSONObject { */ public BigDecimal getBigDecimal(String key) throws JSONException { Object object = this.get(key); - if (object instanceof BigDecimal) { - return (BigDecimal)object; - } - try { - return new BigDecimal(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] could not be converted to BigDecimal.", e); + BigDecimal ret = objectToBigDecimal(object, null); + if (ret != null) { + return ret; } + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal (" + object + ")."); } /** @@ -968,7 +975,7 @@ public class JSONObject { * @return true if JSONObject is empty, otherwise false. */ public boolean isEmpty() { - return map.isEmpty(); + return this.map.isEmpty(); } /** @@ -1113,7 +1120,10 @@ public class JSONObject { /** * Get an optional BigDecimal associated with a key, or the defaultValue if * there is no such key or if its value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. + * string, an attempt will be made to evaluate it as a number. If the value + * is float or double, then the {@link BigDecimal#BigDecimal(double)} + * constructor will be used. See notes on the constructor for conversion + * issues that may arise. * * @param key * A key string. @@ -1123,6 +1133,15 @@ public class JSONObject { */ public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { Object val = this.opt(key); + return objectToBigDecimal(val, defaultValue); + } + + /** + * @param defaultValue + * @param val + * @return + */ + static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) { if (NULL.equals(val)) { return defaultValue; } @@ -1133,6 +1152,10 @@ public class JSONObject { return new BigDecimal((BigInteger) val); } if (val instanceof Double || val instanceof Float){ + final double d = ((Number) val).doubleValue(); + if(Double.isNaN(d)) { + return defaultValue; + } return new BigDecimal(((Number) val).doubleValue()); } if (val instanceof Long || val instanceof Integer @@ -1160,6 +1183,15 @@ public class JSONObject { */ public BigInteger optBigInteger(String key, BigInteger defaultValue) { Object val = this.opt(key); + return objectToBigInteger(val, defaultValue); + } + + /** + * @param defaultValue + * @param val + * @return + */ + static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) { if (NULL.equals(val)) { return defaultValue; } @@ -1170,7 +1202,11 @@ public class JSONObject { return ((BigDecimal) val).toBigInteger(); } if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); + final double d = ((Number) val).doubleValue(); + if(Double.isNaN(d)) { + return defaultValue; + } + return new BigDecimal(d).toBigInteger(); } if (val instanceof Long || val instanceof Integer || val instanceof Short || val instanceof Byte){ @@ -2414,13 +2450,9 @@ public class JSONObject { } else if (value instanceof Number) { // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary final String numberAsString = numberToString((Number) value); - try { - // Use the BigDecimal constructor for its parser to validate the format. - @SuppressWarnings("unused") - BigDecimal testNum = new BigDecimal(numberAsString); - // Close enough to a JSON number that we will use it unquoted + if(NUMBER_PATTERN.matcher(numberAsString).matches()) { writer.write(numberAsString); - } catch (NumberFormatException ex){ + } else { // The Number value is not a valid JSON number. // Instead we will quote it as a string quote(numberAsString, writer); diff --git a/JSONWriter.java b/JSONWriter.java index e487781..8ef6084 100644 --- a/JSONWriter.java +++ b/JSONWriter.java @@ -340,17 +340,13 @@ public class JSONWriter { if (value instanceof Number) { // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex final String numberAsString = JSONObject.numberToString((Number) value); - try { - // Use the BigDecimal constructor for it's parser to validate the format. - @SuppressWarnings("unused") - BigDecimal unused = new BigDecimal(numberAsString); + if(JSONObject.NUMBER_PATTERN.matcher(numberAsString).matches()) { // Close enough to a JSON number that we will return it unquoted return numberAsString; - } catch (NumberFormatException ex){ - // The Number value is not a valid JSON number. - // Instead we will quote it as a string - return JSONObject.quote(numberAsString); } + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + return JSONObject.quote(numberAsString); } if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) {