From 1ab5260a7a1041c56f1aafec56768184bcc1dd70 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Thu, 18 May 2017 14:24:34 -0400 Subject: [PATCH] * Adds methods getNUmber and getFloat to JSONArray and JSONObject * Extracts the stringToNumber logic that the optNumber method uses to reuse it between classes * Fixes -0 issue with optNumber/getNumber --- JSONArray.java | 50 +++++++++++++++-- JSONObject.java | 144 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 151 insertions(+), 43 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index a692ba4..6f7439a 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -248,6 +248,49 @@ public class JSONArray implements Iterable { } } + /** + * Get the float value associated with a key. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public float getFloat(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).floatValue() + : Float.parseFloat(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public Number getNumber(int index) throws JSONException { + Object object = this.get(index); + try { + if (object instanceof Number) { + return (Number)object; + } + return JSONObject.stringToNumber(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + /** * Get the enum value associated with an index. * @@ -266,9 +309,8 @@ public class JSONArray implements Iterable { // JSONException should really take a throwable argument. // If it did, I would re-implement this with the Enum.valueOf // method and place any thrown exception in the JSONException - throw new JSONException("JSONObject[" + JSONObject.quote(Integer.toString(index)) - + "] is not an enum of type " + JSONObject.quote(clazz.getSimpleName()) - + "."); + throw new JSONException("JSONArray[" + index + "] is not an enum of type " + + JSONObject.quote(clazz.getSimpleName()) + "."); } return val; } @@ -845,7 +887,7 @@ public class JSONArray implements Iterable { if (val instanceof String) { try { - return new BigDecimal(val.toString()); + return JSONObject.stringToNumber((String) val); } catch (Exception e) { return defaultValue; } diff --git a/JSONObject.java b/JSONObject.java index be547a7..bba5779 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -588,6 +588,50 @@ public class JSONObject { } } + /** + * Get the float value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public float getFloat(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).floatValue() + : Float.parseFloat(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public Number getNumber(String key) throws JSONException { + Object object = this.get(key); + try { + if (object instanceof Number) { + return (Number)object; + } + return stringToNumber(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number.", e); + } + } + /** * Get the int value associated with a key. * @@ -1267,51 +1311,14 @@ public class JSONObject { if (val instanceof String) { try { - // decimal representation - if (((String)val).indexOf('.')>=0 || ((String)val).indexOf('e')>=0 || ((String)val).indexOf('E')>=0) { - // quick dirty way to see if we need a BigDecimal instead of a Double - if (((String)val).length()>14) { - return new BigDecimal((String)val); - } - return Double.valueOf((String)val); - } - // integer representation. - // This will narrow any values to the smallest reasonable Object representation - // (Integer, Long, or BigInteger) - // The compare string length method reduces GC, - // but leads to smaller integers being placed in larger wrappers even though not - // needed. i.e. 1,000,000,000 -> Long even though it's an Integer - // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long - - // string version - if(((String)val).length()<=9){ - return Integer.valueOf((String)val); - } - if(((String)val).length()<=18){ - return Long.valueOf((String)val); - } - return new BigInteger((String)val); - - // BigInteger version: We use a similar bitLenth compare as - // BigInteger#intValueExact uses. Increases GC, but objects hold - // only what they need. i.e. Less runtime overhead if the value is - // long lived. Which is the better tradeoff? - - //BigInteger bi = new BigInteger((String)val); - //if(bi.bitLength()<=31){ - // return Integer.valueOf(bi.intValue()); - //} - //if(bi.bitLength()<=63){ - // return Long.valueOf(bi.longValue()); - //} - //return bi; + return stringToNumber((String) val); } catch (Exception e) { return defaultValue; } } return defaultValue; } - + /** * Get an optional string associated with a key. It returns an empty string * if there is no such key. If the value is not a string and is not null, @@ -1757,6 +1764,65 @@ public class JSONObject { } } + /** + * Converts a string to a number using the narrowest possible type. Possible + * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. + * + * An Exception is thrown if + * + * @param val value to convert + * @return Number representation of the value. + * @throws NumberFormatException thrown if the value is not a valid number. A public + * caller should catch this and wrap it in a {@link JSONException} if applicable. + */ + protected static Number stringToNumber(final String val) throws NumberFormatException { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + // decimal representation + if (val.indexOf('.') > -1 || val.indexOf('e') > -1 + || val.indexOf('E') > -1 + || "-0".equals(val)) { + // quick dirty way to see if we need a BigDecimal instead of a Double + if (val.length()>14) { + return new BigDecimal(val); + } + return Double.valueOf(val); + } + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) + // The compare string length method reduces GC, + // but leads to smaller integers being placed in larger wrappers even though not + // needed. i.e. 1,000,000,000 -> Long even though it's an Integer + // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long + + // string version + if(val.length()<=9){ + return Integer.valueOf(val); + } + if(val.length()<=18){ + return Long.valueOf(val); + } + return new BigInteger(val); + + // BigInteger version: We use a similar bitLenth compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. Which is the better tradeoff? This is closer to what's + // in stringToValue. + + //BigInteger bi = new BigInteger((String)val); + //if(bi.bitLength()<=31){ + // return Integer.valueOf(bi.intValue()); + //} + //if(bi.bitLength()<=63){ + // return Long.valueOf(bi.longValue()); + //} + //return bi; + } + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + /** * Try to convert a string into a number, boolean, or null. If the string * can't be converted, return the string.