Revert changes with feature and refactor together.

This commit is contained in:
rudrajyoti biswas 2023-10-19 10:28:11 +05:30
parent 7b2677ac5a
commit 1d0775cce7
8 changed files with 176 additions and 322 deletions

View File

@ -331,7 +331,7 @@ public class JSONArray implements Iterable<Object> {
if (object instanceof Number) {
return (Number)object;
}
return NumberConversionUtil.stringToNumber(object.toString());
return JSONObject.stringToNumber(object.toString());
} catch (Exception e) {
throw wrongValueFormatException(index, "number", object, e);
}
@ -1078,7 +1078,7 @@ public class JSONArray implements Iterable<Object> {
if (val instanceof String) {
try {
return NumberConversionUtil.stringToNumber((String) val);
return JSONObject.stringToNumber((String) val);
} catch (Exception e) {
return defaultValue;
}

View File

@ -28,8 +28,6 @@ import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Pattern;
import static org.json.NumberConversionUtil.stringToNumber;
/**
* A JSONObject is an unordered collection of name/value pairs. Its external
* form is a string wrapped in curly braces with colons between the names and
@ -2382,7 +2380,83 @@ public class JSONObject {
|| val.indexOf('E') > -1 || "-0".equals(val);
}
/**
* Converts a string to a number using the narrowest possible type. Possible
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
* When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
*
* @param input 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 input) throws NumberFormatException {
String val = input;
if (val.startsWith(".")){
val = "0"+val;
}
if (val.startsWith("-.")){
val = "-0."+val.substring(2);
}
char initial = val.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-' ) {
// decimal representation
if (isDecimalNotation(val)) {
// 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);
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
return Double.valueOf(-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 ["+input+"] is not a valid number.");
}
return d;
} catch (NumberFormatException ignore) {
throw new NumberFormatException("val ["+input+"] is not a valid number.");
}
}
}
val = removeLeadingZerosOfNumber(input);
initial = val.charAt(0);
if(initial == '0' && val.length() > 1) {
char at1 = val.charAt(1);
if(at1 >= '0' && at1 <= '9') {
throw new NumberFormatException("val ["+input+"] 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 ["+input+"] is not a valid number.");
}
}
// 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 ["+input+"] is not a valid number.");
}
/**
* Try to convert a string into a number, boolean, or null. If the string
@ -2848,4 +2922,23 @@ public class JSONObject {
);
}
/**
* For a prospective number, remove the leading zeros
* @param value prospective number
* @return number without leading zeros
*/
private static String removeLeadingZerosOfNumber(String value){
if (value.equals("-")){return value;}
boolean negativeFirstChar = (value.charAt(0) == '-');
int counter = negativeFirstChar ? 1:0;
while (counter < value.length()){
if (value.charAt(counter) != '0'){
if (negativeFirstChar) {return "-".concat(value.substring(counter));}
return value.substring(counter);
}
++counter;
}
if (negativeFirstChar) {return "-0";}
return "0";
}
}

View File

@ -1,142 +0,0 @@
package org.json;
import java.math.BigDecimal;
import java.math.BigInteger;
public class NumberConversionUtil {
/**
* Converts a string to a number using the narrowest possible type. Possible
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
* When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
*
* @param input 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.
*/
public static Number stringToNumber(final String input) throws NumberFormatException {
String val = input;
if (val.startsWith(".")){
val = "0"+val;
}
if (val.startsWith("-.")){
val = "-0."+val.substring(2);
}
char initial = val.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-' ) {
// decimal representation
if (isDecimalNotation(val)) {
// 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);
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
return Double.valueOf(-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 ["+input+"] is not a valid number.");
}
return d;
} catch (NumberFormatException ignore) {
throw new NumberFormatException("val ["+input+"] is not a valid number.");
}
}
}
val = removeLeadingZerosOfNumber(input);
initial = val.charAt(0);
if(initial == '0' && val.length() > 1) {
char at1 = val.charAt(1);
if(at1 >= '0' && at1 <= '9') {
throw new NumberFormatException("val ["+input+"] 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 ["+input+"] is not a valid number.");
}
}
// 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 ["+input+"] is not a valid number.");
}
/**
* Checks if the value could be considered a number in decimal number system.
* @param value
* @return
*/
public static boolean potentialNumber(String value){
if (value == null || value.isEmpty()){
return false;
}
return potentialPositiveNumberStartingAtIndex(value, (value.charAt(0)=='-'?1:0));
}
/**
* Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
*
* @param val value to test
* @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
*/
private static boolean isDecimalNotation(final String val) {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}
private static boolean potentialPositiveNumberStartingAtIndex(String value,int index){
if (index >= value.length()){
return false;
}
return digitAtIndex(value, (value.charAt(index)=='.'?index+1:index));
}
private static boolean digitAtIndex(String value, int index){
if (index >= value.length()){
return false;
}
return value.charAt(index) >= '0' && value.charAt(index) <= '9';
}
/**
* For a prospective number, remove the leading zeros
* @param value prospective number
* @return number without leading zeros
*/
private static String removeLeadingZerosOfNumber(String value){
if (value.equals("-")){return value;}
boolean negativeFirstChar = (value.charAt(0) == '-');
int counter = negativeFirstChar ? 1:0;
while (counter < value.length()){
if (value.charAt(counter) != '0'){
if (negativeFirstChar) {return "-".concat(value.substring(counter));}
return value.substring(counter);
}
++counter;
}
if (negativeFirstChar) {return "-0";}
return "0";
}
}

View File

@ -6,11 +6,10 @@ Public Domain.
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
import static org.json.NumberConversionUtil.potentialNumber;
import static org.json.NumberConversionUtil.stringToNumber;
/**
* This provides static methods to convert an XML text into a JSONObject, and to
@ -487,7 +486,8 @@ public class XML {
* produced, then the value will just be a string.
*/
if (potentialNumber(string)) {
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
return stringToNumber(string);
} catch (Exception ignore) {
@ -496,6 +496,78 @@ public class XML {
return string;
}
/**
* direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
*/
private static Number stringToNumber(final String val) throws NumberFormatException {
char initial = val.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
// decimal representation
if (isDecimalNotation(val)) {
// 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);
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
return Double.valueOf(-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.");
}
}
// 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.");
}
/**
* direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
*/
private static boolean isDecimalNotation(final String val) {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject. Some information may be lost in this transformation because

View File

@ -709,7 +709,7 @@ public class JSONMLTest {
@Test
public void testToJSONArray_jsonOutput() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final String expectedJsonString = "[\"root\",[\"id\",1],[\"id\",1],[\"id\",0],[\"id\",0],[\"item\",{\"id\":1}],[\"title\",true]]";
final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",1],[\"id\",\"00\"],[\"id\",0],[\"item\",{\"id\":\"01\"}],[\"title\",true]]";
final JSONArray actualJsonOutput = JSONML.toJSONArray(originalXml, false);
assertEquals(expectedJsonString, actualJsonOutput.toString());
}

View File

@ -1,169 +0,0 @@
package org.json.junit;
import org.json.NumberConversionUtil;
import org.junit.Test;
import java.math.BigDecimal;
import java.math.BigInteger;
import static org.junit.Assert.*;
public class NumberConversionUtilTest {
@Test
public void shouldParseDecimalFractionNumbersWithMultipleLeadingZeros(){
Number number = NumberConversionUtil.stringToNumber("00.10d");
assertEquals("Do not match", 0.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", 0.10f, number.floatValue(),0.0f);
assertEquals("Do not match", 0, number.longValue(),0);
assertEquals("Do not match", 0, number.intValue(),0);
}
@Test
public void shouldParseDecimalFractionNumbersWithSingleLeadingZero(){
Number number = NumberConversionUtil.stringToNumber("0.10d");
assertEquals("Do not match", 0.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", 0.10f, number.floatValue(),0.0f);
assertEquals("Do not match", 0, number.longValue(),0);
assertEquals("Do not match", 0, number.intValue(),0);
}
@Test
public void shouldParseDecimalFractionNumbersWithZerosAfterDecimalPoint(){
Number number = NumberConversionUtil.stringToNumber("0.010d");
assertEquals("Do not match", 0.010d, number.doubleValue(),0.0d);
assertEquals("Do not match", 0.010f, number.floatValue(),0.0f);
assertEquals("Do not match", 0, number.longValue(),0);
assertEquals("Do not match", 0, number.intValue(),0);
}
@Test
public void shouldParseMixedDecimalFractionNumbersWithMultipleLeadingZeros(){
Number number = NumberConversionUtil.stringToNumber("00200.10d");
assertEquals("Do not match", 200.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", 200.10f, number.floatValue(),0.0f);
assertEquals("Do not match", 200, number.longValue(),0);
assertEquals("Do not match", 200, number.intValue(),0);
}
@Test
public void shouldParseMixedDecimalFractionNumbersWithoutLeadingZero(){
Number number = NumberConversionUtil.stringToNumber("200.10d");
assertEquals("Do not match", 200.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", 200.10f, number.floatValue(),0.0f);
assertEquals("Do not match", 200, number.longValue(),0);
assertEquals("Do not match", 200, number.intValue(),0);
}
@Test
public void shouldParseMixedDecimalFractionNumbersWithZerosAfterDecimalPoint(){
Number number = NumberConversionUtil.stringToNumber("200.010d");
assertEquals("Do not match", 200.010d, number.doubleValue(),0.0d);
assertEquals("Do not match", 200.010f, number.floatValue(),0.0f);
assertEquals("Do not match", 200, number.longValue(),0);
assertEquals("Do not match", 200, number.intValue(),0);
}
@Test
public void shouldParseNegativeDecimalFractionNumbersWithMultipleLeadingZeros(){
Number number = NumberConversionUtil.stringToNumber("-00.10d");
assertEquals("Do not match", -0.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", -0.10f, number.floatValue(),0.0f);
assertEquals("Do not match", -0, number.longValue(),0);
assertEquals("Do not match", -0, number.intValue(),0);
}
@Test
public void shouldParseNegativeDecimalFractionNumbersWithSingleLeadingZero(){
Number number = NumberConversionUtil.stringToNumber("-0.10d");
assertEquals("Do not match", -0.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", -0.10f, number.floatValue(),0.0f);
assertEquals("Do not match", -0, number.longValue(),0);
assertEquals("Do not match", -0, number.intValue(),0);
}
@Test
public void shouldParseNegativeDecimalFractionNumbersWithZerosAfterDecimalPoint(){
Number number = NumberConversionUtil.stringToNumber("-0.010d");
assertEquals("Do not match", -0.010d, number.doubleValue(),0.0d);
assertEquals("Do not match", -0.010f, number.floatValue(),0.0f);
assertEquals("Do not match", -0, number.longValue(),0);
assertEquals("Do not match", -0, number.intValue(),0);
}
@Test
public void shouldParseNegativeMixedDecimalFractionNumbersWithMultipleLeadingZeros(){
Number number = NumberConversionUtil.stringToNumber("-00200.10d");
assertEquals("Do not match", -200.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", -200.10f, number.floatValue(),0.0f);
assertEquals("Do not match", -200, number.longValue(),0);
assertEquals("Do not match", -200, number.intValue(),0);
}
@Test
public void shouldParseNegativeMixedDecimalFractionNumbersWithoutLeadingZero(){
Number number = NumberConversionUtil.stringToNumber("-200.10d");
assertEquals("Do not match", -200.10d, number.doubleValue(),0.0d);
assertEquals("Do not match", -200.10f, number.floatValue(),0.0f);
assertEquals("Do not match", -200, number.longValue(),0);
assertEquals("Do not match", -200, number.intValue(),0);
}
@Test
public void shouldParseNegativeMixedDecimalFractionNumbersWithZerosAfterDecimalPoint(){
Number number = NumberConversionUtil.stringToNumber("-200.010d");
assertEquals("Do not match", -200.010d, number.doubleValue(),0.0d);
assertEquals("Do not match", -200.010f, number.floatValue(),0.0f);
assertEquals("Do not match", -200, number.longValue(),0);
assertEquals("Do not match", -200, number.intValue(),0);
}
@Test
public void shouldParseNumbersWithExponents(){
Number number = NumberConversionUtil.stringToNumber("23.45e7");
assertEquals("Do not match", 23.45e7d, number.doubleValue(),0.0d);
assertEquals("Do not match", 23.45e7f, number.floatValue(),0.0f);
assertEquals("Do not match", 2.345E8, number.longValue(),0);
assertEquals("Do not match", 2.345E8, number.intValue(),0);
}
@Test
public void shouldParseNegativeNumbersWithExponents(){
Number number = NumberConversionUtil.stringToNumber("-23.45e7");
assertEquals("Do not match", -23.45e7d, number.doubleValue(),0.0d);
assertEquals("Do not match", -23.45e7f, number.floatValue(),0.0f);
assertEquals("Do not match", -2.345E8, number.longValue(),0);
assertEquals("Do not match", -2.345E8, number.intValue(),0);
}
@Test
public void shouldParseBigDecimal(){
Number number = NumberConversionUtil.stringToNumber("19007199254740993.35481234487103587486413587843213584");
assertTrue(number instanceof BigDecimal);
}
@Test
public void shouldParseBigInteger(){
Number number = NumberConversionUtil.stringToNumber("1900719925474099335481234487103587486413587843213584");
assertTrue(number instanceof BigInteger);
}
@Test
public void shouldIdentifyPotentialNumber(){
assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("112.123"));
assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("112e123"));
assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("-112.123"));
assertTrue("Does not identify as number", NumberConversionUtil.potentialNumber("-112e23"));
assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("--112.123"));
assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("-a112.123"));
assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("a112.123"));
assertFalse("Does not identify as not number", NumberConversionUtil.potentialNumber("e112.123"));
}
}

View File

@ -733,7 +733,7 @@ public class XMLConfigurationTest {
@Test
public void testToJSONArray_jsonOutput() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}");
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepStrings(false));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);

View File

@ -791,7 +791,7 @@ public class XMLTest {
@Test
public void testToJSONArray_jsonOutput() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}");
final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false);
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expectedJson);