|
28 | 28 | import java.util.Set; |
29 | 29 | import java.util.regex.Pattern; |
30 | 30 |
|
31 | | -import static org.json.NumberConversionUtil.stringToNumber; |
32 | | - |
33 | 31 | /** |
34 | 32 | * A JSONObject is an unordered collection of name/value pairs. Its external |
35 | 33 | * form is a string wrapped in curly braces with colons between the names and |
@@ -2382,7 +2380,83 @@ protected static boolean isDecimalNotation(final String val) { |
2382 | 2380 | || val.indexOf('E') > -1 || "-0".equals(val); |
2383 | 2381 | } |
2384 | 2382 |
|
2385 | | - |
| 2383 | + /** |
| 2384 | + * Converts a string to a number using the narrowest possible type. Possible |
| 2385 | + * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. |
| 2386 | + * When a Double is returned, it should always be a valid Double and not NaN or +-infinity. |
| 2387 | + * |
| 2388 | + * @param input value to convert |
| 2389 | + * @return Number representation of the value. |
| 2390 | + * @throws NumberFormatException thrown if the value is not a valid number. A public |
| 2391 | + * caller should catch this and wrap it in a {@link JSONException} if applicable. |
| 2392 | + */ |
| 2393 | + protected static Number stringToNumber(final String input) throws NumberFormatException { |
| 2394 | + String val = input; |
| 2395 | + if (val.startsWith(".")){ |
| 2396 | + val = "0"+val; |
| 2397 | + } |
| 2398 | + if (val.startsWith("-.")){ |
| 2399 | + val = "-0."+val.substring(2); |
| 2400 | + } |
| 2401 | + char initial = val.charAt(0); |
| 2402 | + if ((initial >= '0' && initial <= '9') || initial == '-' ) { |
| 2403 | + // decimal representation |
| 2404 | + if (isDecimalNotation(val)) { |
| 2405 | + // Use a BigDecimal all the time so we keep the original |
| 2406 | + // representation. BigDecimal doesn't support -0.0, ensure we |
| 2407 | + // keep that by forcing a decimal. |
| 2408 | + try { |
| 2409 | + BigDecimal bd = new BigDecimal(val); |
| 2410 | + if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) { |
| 2411 | + return Double.valueOf(-0.0); |
| 2412 | + } |
| 2413 | + return bd; |
| 2414 | + } catch (NumberFormatException retryAsDouble) { |
| 2415 | + // this is to support "Hex Floats" like this: 0x1.0P-1074 |
| 2416 | + try { |
| 2417 | + Double d = Double.valueOf(val); |
| 2418 | + if(d.isNaN() || d.isInfinite()) { |
| 2419 | + throw new NumberFormatException("val ["+input+"] is not a valid number."); |
| 2420 | + } |
| 2421 | + return d; |
| 2422 | + } catch (NumberFormatException ignore) { |
| 2423 | + throw new NumberFormatException("val ["+input+"] is not a valid number."); |
| 2424 | + } |
| 2425 | + } |
| 2426 | + } |
| 2427 | + val = removeLeadingZerosOfNumber(input); |
| 2428 | + initial = val.charAt(0); |
| 2429 | + if(initial == '0' && val.length() > 1) { |
| 2430 | + char at1 = val.charAt(1); |
| 2431 | + if(at1 >= '0' && at1 <= '9') { |
| 2432 | + throw new NumberFormatException("val ["+input+"] is not a valid number."); |
| 2433 | + } |
| 2434 | + } else if (initial == '-' && val.length() > 2) { |
| 2435 | + char at1 = val.charAt(1); |
| 2436 | + char at2 = val.charAt(2); |
| 2437 | + if(at1 == '0' && at2 >= '0' && at2 <= '9') { |
| 2438 | + throw new NumberFormatException("val ["+input+"] is not a valid number."); |
| 2439 | + } |
| 2440 | + } |
| 2441 | + // integer representation. |
| 2442 | + // This will narrow any values to the smallest reasonable Object representation |
| 2443 | + // (Integer, Long, or BigInteger) |
| 2444 | + |
| 2445 | + // BigInteger down conversion: We use a similar bitLength compare as |
| 2446 | + // BigInteger#intValueExact uses. Increases GC, but objects hold |
| 2447 | + // only what they need. i.e. Less runtime overhead if the value is |
| 2448 | + // long lived. |
| 2449 | + BigInteger bi = new BigInteger(val); |
| 2450 | + if(bi.bitLength() <= 31){ |
| 2451 | + return Integer.valueOf(bi.intValue()); |
| 2452 | + } |
| 2453 | + if(bi.bitLength() <= 63){ |
| 2454 | + return Long.valueOf(bi.longValue()); |
| 2455 | + } |
| 2456 | + return bi; |
| 2457 | + } |
| 2458 | + throw new NumberFormatException("val ["+input+"] is not a valid number."); |
| 2459 | + } |
2386 | 2460 |
|
2387 | 2461 | /** |
2388 | 2462 | * Try to convert a string into a number, boolean, or null. If the string |
@@ -2848,4 +2922,23 @@ private static JSONException recursivelyDefinedObjectException(String key) { |
2848 | 2922 | ); |
2849 | 2923 | } |
2850 | 2924 |
|
| 2925 | + /** |
| 2926 | + * For a prospective number, remove the leading zeros |
| 2927 | + * @param value prospective number |
| 2928 | + * @return number without leading zeros |
| 2929 | + */ |
| 2930 | + private static String removeLeadingZerosOfNumber(String value){ |
| 2931 | + if (value.equals("-")){return value;} |
| 2932 | + boolean negativeFirstChar = (value.charAt(0) == '-'); |
| 2933 | + int counter = negativeFirstChar ? 1:0; |
| 2934 | + while (counter < value.length()){ |
| 2935 | + if (value.charAt(counter) != '0'){ |
| 2936 | + if (negativeFirstChar) {return "-".concat(value.substring(counter));} |
| 2937 | + return value.substring(counter); |
| 2938 | + } |
| 2939 | + ++counter; |
| 2940 | + } |
| 2941 | + if (negativeFirstChar) {return "-0";} |
| 2942 | + return "0"; |
| 2943 | + } |
2851 | 2944 | } |
0 commit comments