Merge branch 'master' into 863-improve-toString-performance-StringBuilderWriter

This commit is contained in:
Simulant
2024-03-10 21:10:21 +01:00
28 changed files with 605 additions and 714 deletions

View File

@@ -14,22 +14,10 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Pattern;
import static org.json.NumberConversionUtil.potentialNumber;
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
@@ -207,6 +195,21 @@ public class JSONObject {
* duplicated key.
*/
public JSONObject(JSONTokener x) throws JSONException {
this(x, new JSONParserConfiguration());
}
/**
* Construct a JSONObject from a JSONTokener with custom json parse configurations.
*
* @param x
* A JSONTokener object containing the source string.
* @param jsonParserConfiguration
* Variable to pass parser custom configuration for json parsing.
* @throws JSONException
* If there is a syntax error in the source string or a
* duplicated key.
*/
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this();
char c;
String key;
@@ -236,13 +239,14 @@ public class JSONObject {
if (key != null) {
// Check if key exists
if (this.opt(key) != null) {
// key already exists
boolean keyExists = this.opt(key) != null;
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
throw x.syntaxError("Duplicate key \"" + key + "\"");
}
// Only add value if non-null
Object value = x.nextValue();
if (value!=null) {
// Only add value if non-null
if (value != null) {
this.put(key, value);
}
}
@@ -298,7 +302,6 @@ public class JSONObject {
/**
* Construct a JSONObject from a map with recursion depth.
*
*/
private JSONObject(Map<?, ?> m, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
@@ -429,7 +432,25 @@ public class JSONObject {
* duplicated key.
*/
public JSONObject(String source) throws JSONException {
this(new JSONTokener(source));
this(source, new JSONParserConfiguration());
}
/**
* Construct a JSONObject from a source JSON text string with custom json parse configurations.
* This is the most commonly used JSONObject constructor.
*
* @param source
* A string beginning with <code>{</code>&nbsp;<small>(left
* brace)</small> and ending with <code>}</code>
* &nbsp;<small>(right brace)</small>.
* @param jsonParserConfiguration
* Variable to pass parser custom configuration for json parsing.
* @exception JSONException
* If there is a syntax error in the source string or a
* duplicated key.
*/
public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source), jsonParserConfiguration);
}
/**
@@ -2462,7 +2483,8 @@ public class JSONObject {
* 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) {
@@ -2471,8 +2493,75 @@ public class JSONObject {
return string;
}
/**
* 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 val 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 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.");
}
/**
* Throw an exception if the object is a NaN or infinite number.
@@ -2906,5 +2995,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";
}
}