diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 5c37249e3..013be194c 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -36,6 +36,8 @@ of this software and associated documentation files (the "Software"), to deal import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; +import java.text.NumberFormat; +import java.text.ParsePosition; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; @@ -2223,61 +2225,42 @@ protected static boolean isDecimalNotation(final String val) { * 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 == '-') { + String trimmedVal = val.trim(); + char initial = trimmedVal.charAt(0); + if (isNumeric(trimmedVal)) { // decimal representation - if (isDecimalNotation(val)) { + if (isDecimalNotation(trimmedVal)) { // Use a BigDecimal all the time so we keep the original // representation. BigDecimal doesn't support -0.0, ensure we // keep that by forcing a decimal. try { - BigDecimal bd = new BigDecimal(val); + BigDecimal bd = new BigDecimal(trimmedVal); if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) { - return Double.valueOf(-0.0); + return -0.0; } return bd; } catch (NumberFormatException retryAsDouble) { - // this is to support "Hex Floats" like this: 0x1.0P-1074 - try { - Double d = Double.valueOf(val); - if(d.isNaN() || d.isInfinite()) { - throw new NumberFormatException("val ["+val+"] is not a valid number."); - } - return d; - } catch (NumberFormatException ignore) { - throw new NumberFormatException("val ["+val+"] is not a valid number."); - } - } - } - // block items like 00 01 etc. Java number parsers treat these as Octal. - if(initial == '0' && val.length() > 1) { - char at1 = val.charAt(1); - if(at1 >= '0' && at1 <= '9') { throw new NumberFormatException("val ["+val+"] is not a valid number."); } - } else if (initial == '-' && val.length() > 2) { - char at1 = val.charAt(1); - char at2 = val.charAt(2); - if(at1 == '0' && at2 >= '0' && at2 <= '9') { - throw new NumberFormatException("val ["+val+"] is not a valid number."); + } else { + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) + + // BigInteger down conversion: We use a similar bitLength 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. + + BigInteger bi = new BigInteger(trimmedVal); + if(bi.bitLength() <= 31){ + return bi.intValue(); } + if(bi.bitLength() <= 63){ + return bi.longValue(); + } + return bi; } - // integer representation. - // This will narrow any values to the smallest reasonable Object representation - // (Integer, Long, or BigInteger) - - // BigInteger down conversion: We use a similar bitLength 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. - BigInteger bi = new BigInteger(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."); } @@ -2729,4 +2712,10 @@ private static JSONException recursivelyDefinedObjectException(String key) { "JavaBean object contains recursively defined member variable of key " + quote(key) ); } + + private static boolean isNumeric(String str) { + ParsePosition pos = new ParsePosition(0); + NumberFormat.getInstance().parse(str, pos); + return str.length() == pos.getIndex(); + } } diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java index a19961103..553cda8c2 100644 --- a/src/test/java/org/json/junit/JSONStringTest.java +++ b/src/test/java/org/json/junit/JSONStringTest.java @@ -27,6 +27,8 @@ of this software and associated documentation files (the "Software"), to deal import static org.junit.Assert.*; import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.*; import org.json.*; @@ -339,6 +341,108 @@ public void testNullStringValue() throws Exception { } } + /** + * Test for parsing long numerals from String format + * + */ + + @Test + public void testStringLongValue() throws Exception { + JSONObject json = new JSONObject("{\"num1\":\"01234\", \"num2\": \"332211\", \"num3\":\"0987\"}"); + + assertEquals(1234L, json.getLong("num1")); + assertEquals(1234L, json.optLong("num1")); + assertEquals(332211L, json.getLong("num2")); + assertEquals(332211L, json.optLong("num2")); + assertEquals(987, json.getLong("num3")); + assertEquals(987, json.optLong("num3")); + } + + + /** + * Test for parsing int numerals from String format + * + */ + + @Test + public void testStringIntValue() throws Exception { + JSONObject json = new JSONObject("{\"num1\":\"01234\", \"num2\": \"332211\", \"num3\":\"0987\"}"); + + assertEquals(1234, json.getInt("num1")); + assertEquals(1234, json.optInt("num1")); + assertEquals(332211, json.getInt("num2")); + assertEquals(332211, json.optInt("num2")); + assertEquals(987, json.getInt("num3")); + assertEquals(987, json.optInt("num3")); + } + + /** + * Test for parsing double numerals from String format + * + */ + + @Test + public void testStringDoubleValue() throws Exception { + JSONObject json = new JSONObject("{\"num1\":\".123\", \"num2\": \"0.123\", \"num3\":\"8.123\"}"); + + assertEquals(.123, json.getDouble("num1"), 0); + assertEquals(.123, json.optDouble("num1"), 0); + assertEquals(0.123, json.getDouble("num2"), 0); + assertEquals(0.123, json.optDouble("num2"), 0); + assertEquals(8.123, json.getDouble("num3"), 0); + assertEquals(8.123, json.optDouble("num3"), 0); + } + + /** + * Test for parsing float numerals from String format + */ + + @Test + public void testStringFloatValue() throws Exception { + JSONObject json = new JSONObject("{\"num1\":\".123\", \"num2\": \"0.123\", \"num3\":\"8.123\"}"); + + assertEquals(.123, json.getFloat("num1"), 0.000001); + assertEquals(.123, json.optFloat("num1"), 0.000001); + assertEquals(0.123, json.getFloat("num2"), 0.000001); + assertEquals(0.123, json.optFloat("num2"), 0.000001); + assertEquals(8.123, json.getFloat("num3"), 0.000001); + assertEquals(8.123, json.optFloat("num3"), 0.000001); + } + + + /** + * Test for parsing BigDecimal numerals from String format + * + */ + + @Test + public void testStringBigDecimalValue() throws Exception { + JSONObject json = new JSONObject("{\"num1\":\".123\", \"num2\": \"0.123\", \"num3\":\"8.123\"}"); + + assertEquals(new BigDecimal(".123"), json.getBigDecimal("num1")); + assertEquals(new BigDecimal(".123"), json.optBigDecimal("num1", BigDecimal.ONE)); + assertEquals(new BigDecimal("0.123"), json.getBigDecimal("num2")); + assertEquals(new BigDecimal("0.123"), json.optBigDecimal("num2", BigDecimal.ONE)); + assertEquals(new BigDecimal("8.123"), json.getBigDecimal("num3")); + assertEquals(new BigDecimal("8.123"), json.optBigDecimal("num3", BigDecimal.ONE)); + } + + /** + * Test for parsing BigInteger numerals from String format + */ + + @Test + public void testStringBigIntegerValue() throws Exception { + JSONObject json = new JSONObject("{\"num1\":\"01234\", \"num2\": \"332211\", \"num3\":\"0987\"}"); + + assertEquals(BigInteger.valueOf(1234), json.getBigInteger("num1")); + assertEquals(BigInteger.valueOf(1234), json.getBigInteger("num1")); + assertEquals(BigInteger.valueOf(332211), json.getBigInteger("num2")); + assertEquals(BigInteger.valueOf(332211), json.getBigInteger("num2")); + assertEquals(BigInteger.valueOf(987), json.getBigInteger("num3")); + assertEquals(BigInteger.valueOf(987), json.getBigInteger("num3")); + } + /** * A JSONString that returns a valid JSON string value. */