Skip to content

Commit 19bb6fd

Browse files
authored
Merge pull request #453 from johnjaylward/UseBigDecimalDefaultParse
changes number parsing to use BigDecimal as the backing type
2 parents fee6ddb + 56d33b8 commit 19bb6fd

13 files changed

+246
-253
lines changed

src/main/java/org/json/JSONObject.java

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,48 +2109,54 @@ protected static Number stringToNumber(final String val) throws NumberFormatExce
21092109
if ((initial >= '0' && initial <= '9') || initial == '-') {
21102110
// decimal representation
21112111
if (isDecimalNotation(val)) {
2112-
// quick dirty way to see if we need a BigDecimal instead of a Double
2113-
// this only handles some cases of overflow or underflow
2114-
if (val.length()>14) {
2115-
return new BigDecimal(val);
2112+
// Use a BigDecimal all the time so we keep the original
2113+
// representation. BigDecimal doesn't support -0.0, ensure we
2114+
// keep that by forcing a decimal.
2115+
try {
2116+
BigDecimal bd = new BigDecimal(val);
2117+
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
2118+
return Double.valueOf(-0.0);
2119+
}
2120+
return bd;
2121+
} catch (NumberFormatException retryAsDouble) {
2122+
// this is to support "Hex Floats" like this: 0x1.0P-1074
2123+
try {
2124+
Double d = Double.valueOf(val);
2125+
if(d.isNaN() || d.isInfinite()) {
2126+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
2127+
}
2128+
return d;
2129+
} catch (NumberFormatException ignore) {
2130+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
2131+
}
2132+
}
2133+
}
2134+
// block items like 00 01 etc. Java number parsers treat these as Octal.
2135+
if(initial == '0' && val.length() > 1) {
2136+
char at1 = val.charAt(1);
2137+
if(at1 >= '0' && at1 <= '9') {
2138+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
21162139
}
2117-
final Double d = Double.valueOf(val);
2118-
if (d.isInfinite() || d.isNaN()) {
2119-
// if we can't parse it as a double, go up to BigDecimal
2120-
// this is probably due to underflow like 4.32e-678
2121-
// or overflow like 4.65e5324. The size of the string is small
2122-
// but can't be held in a Double.
2123-
return new BigDecimal(val);
2140+
} else if (initial == '-' && val.length() > 2) {
2141+
char at1 = val.charAt(1);
2142+
char at2 = val.charAt(2);
2143+
if(at1 == '0' && at2 >= '0' && at2 <= '9') {
2144+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
21242145
}
2125-
return d;
21262146
}
21272147
// integer representation.
21282148
// This will narrow any values to the smallest reasonable Object representation
21292149
// (Integer, Long, or BigInteger)
21302150

2131-
// string version
2132-
// The compare string length method reduces GC,
2133-
// but leads to smaller integers being placed in larger wrappers even though not
2134-
// needed. i.e. 1,000,000,000 -> Long even though it's an Integer
2135-
// 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
2136-
//if(val.length()<=9){
2137-
// return Integer.valueOf(val);
2138-
//}
2139-
//if(val.length()<=18){
2140-
// return Long.valueOf(val);
2141-
//}
2142-
//return new BigInteger(val);
2143-
2144-
// BigInteger version: We use a similar bitLength compare as
2151+
// BigInteger down conversion: We use a similar bitLenth compare as
21452152
// BigInteger#intValueExact uses. Increases GC, but objects hold
21462153
// only what they need. i.e. Less runtime overhead if the value is
2147-
// long lived. Which is the better tradeoff? This is closer to what's
2148-
// in stringToValue.
2154+
// long lived.
21492155
BigInteger bi = new BigInteger(val);
2150-
if(bi.bitLength()<=31){
2156+
if(bi.bitLength() <= 31){
21512157
return Integer.valueOf(bi.intValue());
21522158
}
2153-
if(bi.bitLength()<=63){
2159+
if(bi.bitLength() <= 63){
21542160
return Long.valueOf(bi.longValue());
21552161
}
21562162
return bi;
@@ -2194,23 +2200,7 @@ public static Object stringToValue(String string) {
21942200
char initial = string.charAt(0);
21952201
if ((initial >= '0' && initial <= '9') || initial == '-') {
21962202
try {
2197-
// if we want full Big Number support the contents of this
2198-
// `try` block can be replaced with:
2199-
// return stringToNumber(string);
2200-
if (isDecimalNotation(string)) {
2201-
Double d = Double.valueOf(string);
2202-
if (!d.isInfinite() && !d.isNaN()) {
2203-
return d;
2204-
}
2205-
} else {
2206-
Long myLong = Long.valueOf(string);
2207-
if (string.equals(myLong.toString())) {
2208-
if (myLong.longValue() == myLong.intValue()) {
2209-
return Integer.valueOf(myLong.intValue());
2210-
}
2211-
return myLong;
2212-
}
2213-
}
2203+
return stringToNumber(string);
22142204
} catch (Exception ignore) {
22152205
}
22162206
}

src/main/java/org/json/XML.java

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ of this software and associated documentation files (the "Software"), to deal
2626

2727
import java.io.Reader;
2828
import java.io.StringReader;
29+
import java.math.BigDecimal;
30+
import java.math.BigInteger;
2931
import java.util.Iterator;
3032

3133
/**
@@ -424,17 +426,20 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
424426
*/
425427
// To maintain compatibility with the Android API, this method is a direct copy of
426428
// the one in JSONObject. Changes made here should be reflected there.
429+
// This method should not make calls out of the XML object.
427430
public static Object stringToValue(String string) {
428-
if (string.equals("")) {
431+
if ("".equals(string)) {
429432
return string;
430433
}
431-
if (string.equalsIgnoreCase("true")) {
434+
435+
// check JSON key words true/false/null
436+
if ("true".equalsIgnoreCase(string)) {
432437
return Boolean.TRUE;
433438
}
434-
if (string.equalsIgnoreCase("false")) {
439+
if ("false".equalsIgnoreCase(string)) {
435440
return Boolean.FALSE;
436441
}
437-
if (string.equalsIgnoreCase("null")) {
442+
if ("null".equalsIgnoreCase(string)) {
438443
return JSONObject.NULL;
439444
}
440445

@@ -446,28 +451,84 @@ public static Object stringToValue(String string) {
446451
char initial = string.charAt(0);
447452
if ((initial >= '0' && initial <= '9') || initial == '-') {
448453
try {
449-
// if we want full Big Number support this block can be replaced with:
450-
// return stringToNumber(string);
451-
if (string.indexOf('.') > -1 || string.indexOf('e') > -1
452-
|| string.indexOf('E') > -1 || "-0".equals(string)) {
453-
Double d = Double.valueOf(string);
454-
if (!d.isInfinite() && !d.isNaN()) {
455-
return d;
454+
return stringToNumber(string);
455+
} catch (Exception ignore) {
456+
}
457+
}
458+
return string;
459+
}
460+
461+
/**
462+
* direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
463+
*/
464+
private static Number stringToNumber(final String val) throws NumberFormatException {
465+
char initial = val.charAt(0);
466+
if ((initial >= '0' && initial <= '9') || initial == '-') {
467+
// decimal representation
468+
if (isDecimalNotation(val)) {
469+
// Use a BigDecimal all the time so we keep the original
470+
// representation. BigDecimal doesn't support -0.0, ensure we
471+
// keep that by forcing a decimal.
472+
try {
473+
BigDecimal bd = new BigDecimal(val);
474+
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
475+
return Double.valueOf(-0.0);
456476
}
457-
} else {
458-
Long myLong = Long.valueOf(string);
459-
if (string.equals(myLong.toString())) {
460-
if (myLong.longValue() == myLong.intValue()) {
461-
return Integer.valueOf(myLong.intValue());
477+
return bd;
478+
} catch (NumberFormatException retryAsDouble) {
479+
// this is to support "Hex Floats" like this: 0x1.0P-1074
480+
try {
481+
Double d = Double.valueOf(val);
482+
if(d.isNaN() || d.isInfinite()) {
483+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
462484
}
463-
return myLong;
485+
return d;
486+
} catch (NumberFormatException ignore) {
487+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
464488
}
465489
}
466-
} catch (Exception ignore) {
467490
}
491+
// block items like 00 01 etc. Java number parsers treat these as Octal.
492+
if(initial == '0' && val.length() > 1) {
493+
char at1 = val.charAt(1);
494+
if(at1 >= '0' && at1 <= '9') {
495+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
496+
}
497+
} else if (initial == '-' && val.length() > 2) {
498+
char at1 = val.charAt(1);
499+
char at2 = val.charAt(2);
500+
if(at1 == '0' && at2 >= '0' && at2 <= '9') {
501+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
502+
}
503+
}
504+
// integer representation.
505+
// This will narrow any values to the smallest reasonable Object representation
506+
// (Integer, Long, or BigInteger)
507+
508+
// BigInteger down conversion: We use a similar bitLenth compare as
509+
// BigInteger#intValueExact uses. Increases GC, but objects hold
510+
// only what they need. i.e. Less runtime overhead if the value is
511+
// long lived.
512+
BigInteger bi = new BigInteger(val);
513+
if(bi.bitLength() <= 31){
514+
return Integer.valueOf(bi.intValue());
515+
}
516+
if(bi.bitLength() <= 63){
517+
return Long.valueOf(bi.longValue());
518+
}
519+
return bi;
468520
}
469-
return string;
521+
throw new NumberFormatException("val ["+val+"] is not a valid number.");
470522
}
523+
524+
/**
525+
* direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
526+
*/
527+
private static boolean isDecimalNotation(final String val) {
528+
return val.indexOf('.') > -1 || val.indexOf('e') > -1
529+
|| val.indexOf('E') > -1 || "-0".equals(val);
530+
}
531+
471532

472533
/**
473534
* Convert a well-formed (but not necessarily valid) XML string into a

0 commit comments

Comments
 (0)