diff --git a/HTTPTokener.java b/HTTPTokener.java index 55f48ffa5..2fab48fba 100644 --- a/HTTPTokener.java +++ b/HTTPTokener.java @@ -1,7 +1,8 @@ package org.json; /* -Copyright (c) 2002 JSON.org +Original work Copyright (c) 2002 JSON.org +Modified work Copyright (c) 2019 Isaias Arellano - isaias.arellano.delgado@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,6 +25,8 @@ of this software and associated documentation files (the "Software"), to deal SOFTWARE. */ +import static org.json.JSONObject.BIGNUMBER_LENGTH; + /** * The HTTPTokener extends the JSONTokener to provide additional methods * for the parsing of HTTP headers. @@ -35,12 +38,33 @@ public class HTTPTokener extends JSONTokener { /** * Construct an HTTPTokener from a string. * @param string A source string. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} */ - public HTTPTokener(String string) { - super(string); + public HTTPTokener(String string, boolean bigNumberEnabled, int bigNumberLength) { + super(string, bigNumberEnabled, bigNumberLength); + } + + /** + * Construct an HTTPTokener from a string. + * @param string A source string. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public HTTPTokener(String string, boolean bigNumberEnabled) { + this(string, bigNumberEnabled, BIGNUMBER_LENGTH); } + /** + * Construct an HTTPTokener from a string. + * @param string A source string. + * + */ + public HTTPTokener(String string) { + this(string, false); + } + /** * Get the next token or string. This is used in parsing HTTP headers. * @throws JSONException diff --git a/JSONArray.java b/JSONArray.java index 537abb134..c827ccde4 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -1,7 +1,8 @@ package org.json; /* - Copyright (c) 2002 JSON.org + Original work Copyright (c) 2002 JSON.org + Modified work Copyright (c) 2019 Isaias Arellano - isaias.arellano.delgado@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -36,6 +37,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.List; import java.util.Map; +import static org.json.JSONObject.BIGNUMBER_LENGTH; /** * A JSONArray is an ordered sequence of values. Its external text form is a @@ -88,6 +90,8 @@ public class JSONArray implements Iterable { */ private final ArrayList myArrayList; + private int bigNumberLength = BIGNUMBER_LENGTH; + /** * Construct an empty JSONArray. */ @@ -95,16 +99,28 @@ public JSONArray() { this.myArrayList = new ArrayList(); } + /** + * Construct an empty JSONArray. + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public JSONArray(int bigNumberLength) { + this(); + this.bigNumberLength = bigNumberLength; + + } + /** * Construct a JSONArray from a JSONTokener. * * @param x * A JSONTokener + * @param bigNumberLength + * Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} * @throws JSONException * If there is a syntax error. */ - public JSONArray(JSONTokener x) throws JSONException { - this(); + public JSONArray(JSONTokener x, int bigNumberLength) throws JSONException { + this(bigNumberLength); if (x.nextClean() != '[') { throw x.syntaxError("A JSONArray text must start with '['"); } @@ -148,6 +164,53 @@ public JSONArray(JSONTokener x) throws JSONException { } } + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x + * A JSONTokener + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(x, BIGNUMBER_LENGTH); + } + + /** + * Construct a JSONArray from a source JSON text, optionally allows to use BigDecimal and BigInteger + * + * @param source + * A string that begins with [ (left + * bracket) and ends with ] + *  (right bracket). + * @param bigNumberEnabled + * Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength + * Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source, boolean bigNumberEnabled, int bigNumberLength) throws JSONException { + this(new JSONTokener(source, bigNumberEnabled, bigNumberLength), bigNumberLength); + } + + /** + * Construct a JSONArray from a source JSON text, optionally allows to use BigDecimal and BigInteger + * + * @param source + * A string that begins with [ (left + * bracket) and ends with ] + *  (right bracket). + * @param bigNumberEnabled + * Enables to parse BigDecimal and BigInteger to avoid precision loss. + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source, boolean bigNumberEnabled) throws JSONException { + this(source, bigNumberEnabled, BIGNUMBER_LENGTH); + } + /** * Construct a JSONArray from a source JSON text. * @@ -159,7 +222,7 @@ public JSONArray(JSONTokener x) throws JSONException { * If there is a syntax error. */ public JSONArray(String source) throws JSONException { - this(new JSONTokener(source)); + this(source, false); } /** @@ -296,7 +359,7 @@ public Number getNumber(int index) throws JSONException { if (object instanceof Number) { return (Number)object; } - return JSONObject.stringToNumber(object.toString()); + return JSONObject.stringToNumber(object.toString(), bigNumberLength); } catch (Exception e) { throw new JSONException("JSONArray[" + index + "] is not a number.", e); } @@ -826,7 +889,7 @@ public Number optNumber(int index, Number defaultValue) { if (val instanceof String) { try { - return JSONObject.stringToNumber((String) val); + return JSONObject.stringToNumber((String) val, bigNumberLength); } catch (Exception e) { return defaultValue; } diff --git a/JSONObject.java b/JSONObject.java index a1ed4901c..075fdd2ff 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -3,7 +3,8 @@ import java.io.Closeable; /* - Copyright (c) 2002 JSON.org + Original work Copyright (c) 2002 JSON.org + Modified work Copyright (c) 2019 Isaias Arellano - isaias.arellano.delgado@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -101,6 +102,9 @@ of this software and associated documentation files (the "Software"), to deal * @version 2016-08-15 */ public class JSONObject { + + public static final int BIGNUMBER_LENGTH = 14; + /** * JSONObject.NULL is equivalent to the value that JavaScript calls null, * whilst Java's null is equivalent to the value that JavaScript calls @@ -171,6 +175,11 @@ public String toString() { */ public static final Object NULL = new Null(); + /** + * Parses number token as BigNumber is token length exceeds this value + */ + private int bigNumberLength; + /** * Construct an empty JSONObject. */ @@ -209,12 +218,15 @@ public JSONObject(JSONObject jo, String[] names) { * * @param x * A JSONTokener object containing the source string. + * @param bigNumberLength + * Token length when it should be considered as BigNumber * @throws JSONException * If there is a syntax error in the source string or a * duplicated key. */ - public JSONObject(JSONTokener x) throws JSONException { + public JSONObject(JSONTokener x, int bigNumberLength) throws JSONException { this(); + this.bigNumberLength = bigNumberLength; char c; String key; @@ -224,13 +236,13 @@ public JSONObject(JSONTokener x) throws JSONException { for (;;) { c = x.nextClean(); switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); } // The key is followed by ':'. @@ -239,9 +251,9 @@ public JSONObject(JSONTokener x) throws JSONException { if (c != ':') { throw x.syntaxError("Expected a ':' after a key"); } - + // Use syntaxError(..) to include error location - + if (key != null) { // Check if key exists if (this.opt(key) != null) { @@ -258,21 +270,36 @@ public JSONObject(JSONTokener x) throws JSONException { // Pairs are separated by ','. switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); + default: + throw x.syntaxError("Expected a ',' or '}'"); } } } + /** + * Construct a JSONObject from a JSONTokener. + * + * Using this constructor does not enable BigNumber support. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(x, BIGNUMBER_LENGTH); + } + /** * Construct a JSONObject from a Map. * @@ -398,12 +425,52 @@ public JSONObject(Object object, String names[]) { * A string beginning with { (left * brace) and ending with } *  (right brace). + * @param bigNumberEnabled + * If numbers should be attempted to be parsed as BigNumbers + * @param bigNumberLength + * Token length when it should be considered as BigNumber * @exception JSONException * If there is a syntax error in the source string or a * duplicated key. */ + public JSONObject(String source, boolean bigNumberEnabled, int bigNumberLength) throws JSONException { + this(new JSONTokener(source, bigNumberEnabled, bigNumberLength), bigNumberLength); + } + + /** + * Construct a JSONObject from a source JSON text string. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace).* + * @param bigNumberEnabled + * If numbers should be attempted to be parsed as BigNumbers. + * If the, the default legnth of token to be considered as BigNumber is 14 + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source, boolean bigNumberEnabled) throws JSONException { + this(new JSONTokener(source, bigNumberEnabled, BIGNUMBER_LENGTH), BIGNUMBER_LENGTH); + } + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * Using this constructor does not enable BigNumber support. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace).* + * + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ public JSONObject(String source) throws JSONException { - this(new JSONTokener(source)); + this(new JSONTokener(source, false, BIGNUMBER_LENGTH), BIGNUMBER_LENGTH); } /** @@ -486,12 +553,12 @@ public JSONObject accumulate(String key, Object value) throws JSONException { Object object = this.opt(key); if (object == null) { this.put(key, - value instanceof JSONArray ? new JSONArray().put(value) + value instanceof JSONArray ? new JSONArray(this.bigNumberLength).put(value) : value); } else if (object instanceof JSONArray) { ((JSONArray) object).put(value); } else { - this.put(key, new JSONArray().put(object).put(value)); + this.put(key, new JSONArray(this.bigNumberLength).put(object).put(value)); } return this; } @@ -517,7 +584,7 @@ public JSONObject append(String key, Object value) throws JSONException { testValidity(value); Object object = this.opt(key); if (object == null) { - this.put(key, new JSONArray().put(value)); + this.put(key, new JSONArray(this.bigNumberLength).put(value)); } else if (object instanceof JSONArray) { this.put(key, ((JSONArray) object).put(value)); } else { @@ -714,7 +781,7 @@ public Number getNumber(String key) throws JSONException { if (object instanceof Number) { return (Number)object; } - return stringToNumber(object.toString()); + return stringToNumber(object.toString(), bigNumberLength); } catch (Exception e) { throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); @@ -1412,7 +1479,7 @@ public Number optNumber(String key, Number defaultValue) { } try { - return stringToNumber(val.toString()); + return stringToNumber(val.toString(), bigNumberLength); } catch (Exception e) { return defaultValue; } @@ -1595,9 +1662,6 @@ private static A getAnnotation(final Method m, final Clas * implementations and interfaces has the annotation. Returns the depth of the * annotation in the hierarchy. * - * @param - * type of the annotation - * * @param m * method to check * @param annotationClass @@ -2078,14 +2142,14 @@ protected static boolean isDecimalNotation(final String val) { * @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 { + protected static Number stringToNumber(final String val, int bigNumberLength) throws NumberFormatException { char initial = val.charAt(0); if ((initial >= '0' && initial <= '9') || initial == '-') { // decimal representation if (isDecimalNotation(val)) { // quick dirty way to see if we need a BigDecimal instead of a Double // this only handles some cases of overflow or underflow - if (val.length()>14) { + if (val.length()>bigNumberLength) { return new BigDecimal(val); } final Double d = Double.valueOf(val); @@ -2144,7 +2208,7 @@ protected static Number stringToNumber(final String val) throws NumberFormatExce */ // Changes to this method must be copied to the corresponding method in // the XML class to keep full support for Android - public static Object stringToValue(String string) { + public static Object stringToValue(String string, boolean bigNumberEnabled, int bigNumberLength) { if ("".equals(string)) { return string; } @@ -2168,21 +2232,22 @@ public static Object stringToValue(String string) { char initial = string.charAt(0); if ((initial >= '0' && initial <= '9') || initial == '-') { try { - // if we want full Big Number support the contents of this - // `try` block can be replaced with: - // return stringToNumber(string); - if (isDecimalNotation(string)) { - Double d = Double.valueOf(string); - if (!d.isInfinite() && !d.isNaN()) { - return d; - } + if (bigNumberEnabled) { + return stringToNumber(string, bigNumberLength); } else { - Long myLong = Long.valueOf(string); - if (string.equals(myLong.toString())) { - if (myLong.longValue() == myLong.intValue()) { - return Integer.valueOf(myLong.intValue()); + if (isDecimalNotation(string)) { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return Integer.valueOf(myLong.intValue()); + } + return myLong; } - return myLong; } } } catch (Exception ignore) { @@ -2191,6 +2256,10 @@ public static Object stringToValue(String string) { return string; } + public static Object stringToValue(String string) { + return stringToValue(string, false, BIGNUMBER_LENGTH); + } + /** * Throw an exception if the object is a NaN or infinite number. * @@ -2230,7 +2299,7 @@ public JSONArray toJSONArray(JSONArray names) throws JSONException { if (names == null || names.isEmpty()) { return null; } - JSONArray ja = new JSONArray(); + JSONArray ja = new JSONArray(this.bigNumberLength); for (int i = 0; i < names.length(); i += 1) { ja.put(this.opt(names.getString(i))); } diff --git a/JSONTokener.java b/JSONTokener.java index e6821de32..9643b4c82 100644 --- a/JSONTokener.java +++ b/JSONTokener.java @@ -7,8 +7,11 @@ import java.io.Reader; import java.io.StringReader; +import static org.json.JSONObject.BIGNUMBER_LENGTH; + /* -Copyright (c) 2002 JSON.org +Original work Copyright (c) 2002 JSON.org +Modified work Copyright (c) 2019 Isaias Arellano - isaias.arellano.delgado@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -55,17 +58,19 @@ public class JSONTokener { private boolean usePrevious; /** the number of characters read in the previous line. */ private long characterPreviousLine; - + private boolean bigNumberEnabled; + private int bigNumberLength; /** - * Construct a JSONTokener from a Reader. The caller must close the Reader. - * - * @param reader A reader. + * Construct an JSONTokener from a Reader, optionally allows to use BigDecimal and BigInteger + * @param reader A source reader. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} */ - public JSONTokener(Reader reader) { + public JSONTokener(Reader reader, boolean bigNumberEnabled, int bigNumberLength) { this.reader = reader.markSupported() ? reader - : new BufferedReader(reader); + : new BufferedReader(reader); this.eof = false; this.usePrevious = false; this.previous = 0; @@ -73,25 +78,87 @@ public JSONTokener(Reader reader) { this.character = 1; this.characterPreviousLine = 0; this.line = 1; + this.bigNumberEnabled = bigNumberEnabled; + this.bigNumberLength = bigNumberLength; } + /** + * Construct an JSONTokener from a Reader, optionally allows to use BigDecimal and BigInteger + * @param reader A source reader. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public JSONTokener(Reader reader, boolean bigNumberEnabled) { + this(reader, bigNumberEnabled, BIGNUMBER_LENGTH); + } + + /** + * Construct a JSONTokener from a Reader. The caller must close the Reader. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this(reader, false, BIGNUMBER_LENGTH); + } + + /** + * Construct a JSONTokener from an InputStream. The caller must close the input stream. + * @param inputStream The source. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public JSONTokener(InputStream inputStream, boolean bigNumberEnabled, int bigNumberLength) { + this(new InputStreamReader(inputStream), bigNumberEnabled, bigNumberLength); + } + + /** + * Construct a JSONTokener from an InputStream. The caller must close the input stream. + * @param inputStream The source. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public JSONTokener(InputStream inputStream, boolean bigNumberEnabled) { + this(inputStream, bigNumberEnabled, BIGNUMBER_LENGTH); + } /** * Construct a JSONTokener from an InputStream. The caller must close the input stream. * @param inputStream The source. */ public JSONTokener(InputStream inputStream) { - this(new InputStreamReader(inputStream)); + this(inputStream, false); } + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public JSONTokener(String s, boolean bigNumberEnabled, int bigNumberLength) { + this(new StringReader(s), bigNumberEnabled, bigNumberLength); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public JSONTokener(String s, boolean bigNumberEnabled) { + this(s, bigNumberEnabled, BIGNUMBER_LENGTH); + } + /** * Construct a JSONTokener from a string. * * @param s A source string. */ public JSONTokener(String s) { - this(new StringReader(s)); + this(s, false); } @@ -431,7 +498,7 @@ public Object nextValue() throws JSONException { return new JSONObject(this); case '[': this.back(); - return new JSONArray(this); + return new JSONArray(this, this.bigNumberLength); } /* @@ -456,7 +523,7 @@ public Object nextValue() throws JSONException { if ("".equals(string)) { throw this.syntaxError("Missing value"); } - return JSONObject.stringToValue(string); + return JSONObject.stringToValue(string, bigNumberEnabled, bigNumberLength); } diff --git a/XMLTokener.java b/XMLTokener.java index 8490becac..247dedd5d 100644 --- a/XMLTokener.java +++ b/XMLTokener.java @@ -1,7 +1,8 @@ package org.json; /* -Copyright (c) 2002 JSON.org +Original work Copyright (c) 2002 JSON.org +Modified work Copyright (c) 2019 Isaias Arellano - isaias.arellano.delgado@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,6 +27,8 @@ of this software and associated documentation files (the "Software"), to deal import java.io.Reader; +import static org.json.JSONObject.BIGNUMBER_LENGTH; + /** * The XMLTokener extends the JSONTokener to provide additional methods * for the parsing of XML texts. @@ -49,12 +52,52 @@ public class XMLTokener extends JSONTokener { entity.put("quot", XML.QUOT); } + /** + * Construct an XMLTokener from a Reader, optionally allows to use BigDecimal and BigInteger + * @param reader A source reader. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public XMLTokener(Reader reader, boolean bigNumberEnabled, int bigNumberLength) { + super(reader, bigNumberEnabled, bigNumberLength); + } + + /** + * Construct an XMLTokener from a Reader, optionally allows to use BigDecimal and BigInteger + * @param reader A source reader. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public XMLTokener(Reader reader, boolean bigNumberEnabled) { + this(reader, bigNumberEnabled, BIGNUMBER_LENGTH); + } + /** * Construct an XMLTokener from a Reader. - * @param r A source reader. + * @param reader A source reader. + */ + public XMLTokener(Reader reader) { + this(reader, false, BIGNUMBER_LENGTH); + } + + /** + * Construct an XMLTokener from a string, optionally allows to use BigDecimal and BigInteger + * @param s A source string. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * @param bigNumberLength Token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} + */ + public XMLTokener(String s, boolean bigNumberEnabled, int bigNumberLength) { + super(s, bigNumberEnabled, bigNumberLength); + } + + /** + * Construct an XMLTokener from a string, optionally allows to use BigDecimal and BigInteger. + * @param s A source string. + * @param bigNumberEnabled Enables to parse BigDecimal and BigInteger to avoid precision loss + * Default token length to consider a number as BigDecimal or BigInteger is {@value org.json.JSONObject#BIGNUMBER_LENGTH} */ - public XMLTokener(Reader r) { - super(r); + public XMLTokener(String s, boolean bigNumberEnabled) { + this(s, bigNumberEnabled, BIGNUMBER_LENGTH); } /** @@ -62,7 +105,7 @@ public XMLTokener(Reader r) { * @param s A source string. */ public XMLTokener(String s) { - super(s); + this(s, false, BIGNUMBER_LENGTH); } /**