Skip to content

Commit 1ab5260

Browse files
author
John J. Aylward
committed
* 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
1 parent c28a2bd commit 1ab5260

2 files changed

Lines changed: 151 additions & 43 deletions

File tree

JSONArray.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,49 @@ public double getDouble(int index) throws JSONException {
248248
}
249249
}
250250

251+
/**
252+
* Get the float value associated with a key.
253+
*
254+
* @param index
255+
* The index must be between 0 and length() - 1.
256+
* @return The numeric value.
257+
* @throws JSONException
258+
* if the key is not found or if the value is not a Number
259+
* object and cannot be converted to a number.
260+
*/
261+
public float getFloat(int index) throws JSONException {
262+
Object object = this.get(index);
263+
try {
264+
return object instanceof Number ? ((Number) object).floatValue()
265+
: Float.parseFloat(object.toString());
266+
} catch (Exception e) {
267+
throw new JSONException("JSONArray[" + index
268+
+ "] is not a number.", e);
269+
}
270+
}
271+
272+
/**
273+
* Get the Number value associated with a key.
274+
*
275+
* @param index
276+
* The index must be between 0 and length() - 1.
277+
* @return The numeric value.
278+
* @throws JSONException
279+
* if the key is not found or if the value is not a Number
280+
* object and cannot be converted to a number.
281+
*/
282+
public Number getNumber(int index) throws JSONException {
283+
Object object = this.get(index);
284+
try {
285+
if (object instanceof Number) {
286+
return (Number)object;
287+
}
288+
return JSONObject.stringToNumber(object.toString());
289+
} catch (Exception e) {
290+
throw new JSONException("JSONArray[" + index + "] is not a number.");
291+
}
292+
}
293+
251294
/**
252295
* Get the enum value associated with an index.
253296
*
@@ -266,9 +309,8 @@ public <E extends Enum<E>> E getEnum(Class<E> clazz, int index) throws JSONExcep
266309
// JSONException should really take a throwable argument.
267310
// If it did, I would re-implement this with the Enum.valueOf
268311
// method and place any thrown exception in the JSONException
269-
throw new JSONException("JSONObject[" + JSONObject.quote(Integer.toString(index))
270-
+ "] is not an enum of type " + JSONObject.quote(clazz.getSimpleName())
271-
+ ".");
312+
throw new JSONException("JSONArray[" + index + "] is not an enum of type "
313+
+ JSONObject.quote(clazz.getSimpleName()) + ".");
272314
}
273315
return val;
274316
}
@@ -845,7 +887,7 @@ public Number optNumber(int index, Number defaultValue) {
845887

846888
if (val instanceof String) {
847889
try {
848-
return new BigDecimal(val.toString());
890+
return JSONObject.stringToNumber((String) val);
849891
} catch (Exception e) {
850892
return defaultValue;
851893
}

JSONObject.java

Lines changed: 105 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,50 @@ public double getDouble(String key) throws JSONException {
588588
}
589589
}
590590

591+
/**
592+
* Get the float value associated with a key.
593+
*
594+
* @param key
595+
* A key string.
596+
* @return The numeric value.
597+
* @throws JSONException
598+
* if the key is not found or if the value is not a Number
599+
* object and cannot be converted to a number.
600+
*/
601+
public float getFloat(String key) throws JSONException {
602+
Object object = this.get(key);
603+
try {
604+
return object instanceof Number ? ((Number) object).floatValue()
605+
: Float.parseFloat(object.toString());
606+
} catch (Exception e) {
607+
throw new JSONException("JSONObject[" + quote(key)
608+
+ "] is not a number.", e);
609+
}
610+
}
611+
612+
/**
613+
* Get the Number value associated with a key.
614+
*
615+
* @param key
616+
* A key string.
617+
* @return The numeric value.
618+
* @throws JSONException
619+
* if the key is not found or if the value is not a Number
620+
* object and cannot be converted to a number.
621+
*/
622+
public Number getNumber(String key) throws JSONException {
623+
Object object = this.get(key);
624+
try {
625+
if (object instanceof Number) {
626+
return (Number)object;
627+
}
628+
return stringToNumber(object.toString());
629+
} catch (Exception e) {
630+
throw new JSONException("JSONObject[" + quote(key)
631+
+ "] is not a number.", e);
632+
}
633+
}
634+
591635
/**
592636
* Get the int value associated with a key.
593637
*
@@ -1267,51 +1311,14 @@ public Number optNumber(String key, Number defaultValue) {
12671311

12681312
if (val instanceof String) {
12691313
try {
1270-
// decimal representation
1271-
if (((String)val).indexOf('.')>=0 || ((String)val).indexOf('e')>=0 || ((String)val).indexOf('E')>=0) {
1272-
// quick dirty way to see if we need a BigDecimal instead of a Double
1273-
if (((String)val).length()>14) {
1274-
return new BigDecimal((String)val);
1275-
}
1276-
return Double.valueOf((String)val);
1277-
}
1278-
// integer representation.
1279-
// This will narrow any values to the smallest reasonable Object representation
1280-
// (Integer, Long, or BigInteger)
1281-
// The compare string length method reduces GC,
1282-
// but leads to smaller integers being placed in larger wrappers even though not
1283-
// needed. i.e. 1,000,000,000 -> Long even though it's an Integer
1284-
// 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
1285-
1286-
// string version
1287-
if(((String)val).length()<=9){
1288-
return Integer.valueOf((String)val);
1289-
}
1290-
if(((String)val).length()<=18){
1291-
return Long.valueOf((String)val);
1292-
}
1293-
return new BigInteger((String)val);
1294-
1295-
// BigInteger version: We use a similar bitLenth compare as
1296-
// BigInteger#intValueExact uses. Increases GC, but objects hold
1297-
// only what they need. i.e. Less runtime overhead if the value is
1298-
// long lived. Which is the better tradeoff?
1299-
1300-
//BigInteger bi = new BigInteger((String)val);
1301-
//if(bi.bitLength()<=31){
1302-
// return Integer.valueOf(bi.intValue());
1303-
//}
1304-
//if(bi.bitLength()<=63){
1305-
// return Long.valueOf(bi.longValue());
1306-
//}
1307-
//return bi;
1314+
return stringToNumber((String) val);
13081315
} catch (Exception e) {
13091316
return defaultValue;
13101317
}
13111318
}
13121319
return defaultValue;
13131320
}
1314-
1321+
13151322
/**
13161323
* Get an optional string associated with a key. It returns an empty string
13171324
* if there is no such key. If the value is not a string and is not null,
@@ -1757,6 +1764,65 @@ public boolean similar(Object other) {
17571764
}
17581765
}
17591766

1767+
/**
1768+
* Converts a string to a number using the narrowest possible type. Possible
1769+
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
1770+
*
1771+
* An Exception is thrown if
1772+
*
1773+
* @param val value to convert
1774+
* @return Number representation of the value.
1775+
* @throws NumberFormatException thrown if the value is not a valid number. A public
1776+
* caller should catch this and wrap it in a {@link JSONException} if applicable.
1777+
*/
1778+
protected static Number stringToNumber(final String val) throws NumberFormatException {
1779+
char initial = val.charAt(0);
1780+
if ((initial >= '0' && initial <= '9') || initial == '-') {
1781+
// decimal representation
1782+
if (val.indexOf('.') > -1 || val.indexOf('e') > -1
1783+
|| val.indexOf('E') > -1
1784+
|| "-0".equals(val)) {
1785+
// quick dirty way to see if we need a BigDecimal instead of a Double
1786+
if (val.length()>14) {
1787+
return new BigDecimal(val);
1788+
}
1789+
return Double.valueOf(val);
1790+
}
1791+
// integer representation.
1792+
// This will narrow any values to the smallest reasonable Object representation
1793+
// (Integer, Long, or BigInteger)
1794+
// The compare string length method reduces GC,
1795+
// but leads to smaller integers being placed in larger wrappers even though not
1796+
// needed. i.e. 1,000,000,000 -> Long even though it's an Integer
1797+
// 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
1798+
1799+
// string version
1800+
if(val.length()<=9){
1801+
return Integer.valueOf(val);
1802+
}
1803+
if(val.length()<=18){
1804+
return Long.valueOf(val);
1805+
}
1806+
return new BigInteger(val);
1807+
1808+
// BigInteger version: We use a similar bitLenth compare as
1809+
// BigInteger#intValueExact uses. Increases GC, but objects hold
1810+
// only what they need. i.e. Less runtime overhead if the value is
1811+
// long lived. Which is the better tradeoff? This is closer to what's
1812+
// in stringToValue.
1813+
1814+
//BigInteger bi = new BigInteger((String)val);
1815+
//if(bi.bitLength()<=31){
1816+
// return Integer.valueOf(bi.intValue());
1817+
//}
1818+
//if(bi.bitLength()<=63){
1819+
// return Long.valueOf(bi.longValue());
1820+
//}
1821+
//return bi;
1822+
}
1823+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
1824+
}
1825+
17601826
/**
17611827
* Try to convert a string into a number, boolean, or null. If the string
17621828
* can't be converted, return the string.

0 commit comments

Comments
 (0)