107 Commits

Author SHA1 Message Date
Sean Leary
61cdfefc36 Update README.md 2018-01-30 19:46:24 -06:00
Sean Leary
fbad2d0017 Merge pull request #392 from philippgille/patch-1
Remove wrong apostrophe
2018-01-20 03:39:07 -06:00
Philipp Gillé
15719886f7 Remove wrong apostrophe 2018-01-17 18:41:48 +01:00
Sean Leary
ca45b02ffc Merge pull request #381 from fleipold/maven-badge
Adding maven badge to readme
2018-01-14 10:18:46 -06:00
Felix Leipold
b6efbabc32 Making links markdown links 2017-11-17 12:47:49 +01:00
Felix Leipold
9eb8c27724 Marking up class and method names as inline code 2017-11-17 12:33:50 +01:00
Felix Leipold
195963357c Make file names bold 2017-11-17 12:24:17 +01:00
Felix Leipold
28efdb4860 Moving Badge below title 2017-11-17 12:24:03 +01:00
Felix Leipold
c88653ca2e History with fixed font 2017-11-14 17:23:53 +01:00
Felix Leipold
b3068d9fe4 Marking file and class names with single quotes 2017-11-14 17:23:52 +01:00
Felix Leipold
dba4afd0cf Adding maven repo badge 2017-11-14 17:23:36 +01:00
Felix Leipold
26160e1619 Remove trailing whitespace 2017-11-14 17:23:36 +01:00
Felix Leipold
b7e2eee4d6 Renaming README to README.md 2017-11-14 17:23:36 +01:00
Sean Leary
28e09dc493 Merge pull request #380 from johnjaylward/FixFalsePositiveSimilar
Fix for false positives in similar functions
2017-11-11 16:12:06 -06:00
John J. Aylward
4a4b2db8c1 fix for issue #379 2017-11-06 10:28:28 -05:00
Sean Leary
f16682bf44 Merge pull request #375 from johnjaylward/FixExceptionWrapping
fixes wrapped exceptions
2017-11-02 21:42:29 -05:00
John J. Aylward
18952b5ac0 fixes wrapped exceptions 2017-11-02 22:32:24 -04:00
Sean Leary
d2a66a4287 Merge pull request #373 from johnjaylward/UnclosedJSONArray
Fixes Unclosed json array stack overflow
2017-11-02 20:02:26 -05:00
Sean Leary
722003d479 Merge pull request #350 from johnjaylward/AndroidSupport
Updates for supporting the Android API
2017-10-30 11:33:10 -05:00
John J. Aylward
ed8745cd63 fixes #372.
Corrects behavior of unclosed arrays
2017-10-30 08:18:59 -04:00
John J. Aylward
057e0c75ca Merge remote-tracking branch 'origin/master' into AndroidSupport 2017-10-27 13:28:20 -04:00
Sean Leary
cdf3cf7f81 Update README 2017-10-17 20:05:29 -05:00
Sean Leary
2565abdaaa Merge pull request #362 from johnjaylward/FixXMLUnescape
Fixes XML Unescaping
2017-08-27 10:59:26 -05:00
John J. Aylward
de855c50aa Fixes #361.
* Removes unescape from the XML class calls
* fixes bug with unescape method
* moves unescape logic into the XMLTokener class for more consistency
2017-08-19 18:23:07 -04:00
Sean Leary
4cb1ae802a Merge pull request #360 from migueltt/tokener-fix
Creating a JSONObject from a string that contains a duplicate key (any level) throws a JSONException that includes location
2017-08-17 21:30:01 -05:00
Miguel
2e0a8137bd Removed JSONTokener.back() 2017-08-14 13:01:31 -04:00
Miguel
f177c97258 Replacing tabs with 4-spaces 2017-08-10 19:12:41 -04:00
Miguel
7d8353401a Adding JSONTokener.back() just before throwing JSONException
This forces JSONTokener.syntaxError(..) to point to the last character of the duplicate key.
2017-08-10 19:05:57 -04:00
Miguel
7fed023080 Update to include error location when creating JSONObject from string/text 2017-08-09 21:52:36 -04:00
Sean Leary
d9b8507e6a Merge pull request #354 from johnjaylward/PopulateMapMoreStrict
Updates for populateMap based on discussion in #279 and #264
2017-07-19 18:57:32 -05:00
Sean Leary
d345bc528e Merge pull request #357 from johnjaylward/WriteJavadocUpdate
Update javadoc according to issue #356.
2017-07-15 18:51:18 -05:00
John J. Aylward
6f238a3698 Update javadoc according to issue #356. 2017-07-15 12:17:27 -04:00
John J. Aylward
4dbc5ef803 fixes malformed javadoc 2017-07-09 18:48:40 -04:00
John J. Aylward
5c80c9157d fixes malformed javadoc 2017-07-09 18:47:09 -04:00
John J. Aylward
a129ebe8e4 Adds check for resources opened by our bean mapping 2017-07-09 18:09:14 -04:00
John J. Aylward
641b68dd55 updates javadoc. 2017-07-07 21:33:46 -04:00
John J. Aylward
643b25140f Updates for populateMap based on discussion in #279 and #264 2017-07-07 20:48:42 -04:00
John J. Aylward
3997a90d58 update constructor call to match Android implementation 2017-07-07 12:24:27 -04:00
John J. Aylward
1736a60ffe adds comment for the API change 2017-07-07 12:17:45 -04:00
John J. Aylward
e8b1b66888 Updates for supporting the Android API 2017-07-07 12:17:39 -04:00
Sean Leary
5024f2d210 Merge pull request #352 from johnjaylward/ErrorMessagePositionFixes
Error message position fixes
2017-07-06 18:07:50 -05:00
John J. Aylward
16baa323cf adds comments 2017-07-03 13:03:02 -04:00
John J. Aylward
52845366bd Fixes more position errors from stepping to new lines and then back. 2017-07-03 13:03:02 -04:00
John J. Aylward
e7e6ed9205 Fixes position reports on errors 2017-06-23 13:40:41 -04:00
Sean Leary
1add1247fa Merge pull request #348 from johnjaylward/ArrayPerformance
Capacity improvements for internal structures
2017-06-12 02:05:26 -05:00
Sean Leary
5b2e5e7579 Merge pull request #347 from ttulka/master
a comment added to explain the use of HashMap
2017-06-11 23:32:06 -05:00
Sean Leary
c9ae1f17d7 Merge pull request #345 from johnjaylward/BetterErrorHandling
Adds JSONException for write value errors
2017-06-11 14:12:48 -05:00
Tomas Tulka
246350bbcd comment added to explain the reason that JSON object is unordered
to avoid implementators' misconceptions and tries to reimplement the
JSON object to keep the elements order
2017-06-09 09:00:17 +02:00
John J. Aylward
2fbe4d96cf change JSONObject(Map) constructor to use the default capacity when a null map is passed 2017-06-08 12:18:04 -04:00
John J. Aylward
3645f91b55 change JSONArray(Collection) constructor to use the default capacity when a null collection is passed 2017-06-08 12:15:03 -04:00
John J. Aylward
9c092753b0 * Updates array constructor and bulk operations to best guess capacity information
* Update JSONObject to allow best guess for initial capacity.
2017-06-08 11:22:23 -04:00
Tomas Tulka
d0f5607998 a comment added to explain the use of HashMap
to avoid misconception of contributors about using HashMap to implement
a JSON object as a unordered collection by the definition
2017-06-08 08:03:14 +02:00
John J. Aylward
ad6bdd715d Adds JSONException for write value errors so serialization errors can be tracked easier 2017-06-05 20:51:57 -04:00
Sean Leary
ef7a5e40be Merge pull request #341 from johnjaylward/OptimizeLoops
Sorry for the late merge, somehow lost track of this pull request.
2017-05-31 20:51:20 -05:00
John J. Aylward
237bf0adb6 more comments 2017-05-31 18:31:02 -04:00
John J. Aylward
f76fbe7005 fixes comments 2017-05-31 18:13:40 -04:00
John J. Aylward
4f5bf16676 * Adds protected entrySet accessor to JSONObject
* Updates loops that request key/value pairs to use the new entrySet accessor
2017-05-23 12:48:44 -04:00
Sean Leary
fbd2be7431 Merge pull request #337 from johnjaylward/OptimizeOpt
Optimizes opt* functions
2017-05-22 22:59:04 -05:00
John J. Aylward
757b6edb03 Merge branch 'master' of github.com:stleary/JSON-java into OptimizeOpt 2017-05-21 13:12:24 -04:00
Sean Leary
f2b642a1ca Merge pull request #336 from johnjaylward/fixSpelling
Numeric enhancements, Refactoring, Fix spelling
2017-05-20 12:37:31 -05:00
John J. Aylward
04d6e83fc2 * Missed JSONArray optFloat and optDouble for the revert
* prevents erasure of stack trace for rethrown exceptions
2017-05-19 09:49:22 -04:00
John J. Aylward
849b392c01 updates the getNumber/optNumber to not return invalid Doubles 2017-05-18 19:49:50 -04:00
John J. Aylward
a7f8ff24df correct string check for JSONObject optBigDecimal and optBigInteger 2017-05-18 14:41:42 -04:00
John J. Aylward
1ab5260a7a * Adds methods getNUmber and getFloat to JSONArray and JSONObject
* Extracts the stringToNumber logic that the optNumber method uses to reuse it between classes
* Fixes -0 issue with optNumber/getNumber
2017-05-18 14:24:34 -04:00
John J. Aylward
c28a2bdf39 * reverts changes to getDouble and related optDouble and optFloat
* Updates optNumber to be smarter about which object it uses to parse strings
2017-05-18 13:07:32 -04:00
John J. Aylward
382f62e781 * Prevent exceptions in cases where the value is not a string.
* Don't call toString when we know it's a string, just cast
2017-05-18 11:41:51 -04:00
John J. Aylward
0c7bd725a6 fixes for javadoc 2017-05-17 11:34:37 -04:00
John J. Aylward
fcdb8671b2 grr, forgot to save changes on last commit 2017-05-17 11:32:44 -04:00
John J. Aylward
c46774cf13 * Update opt* methods for JSONArray
* Add support to JSONArray and JSONObject to optionally get raw number values
* Add support to JSONArray and JSONObject to optionally get float values
2017-05-17 11:29:26 -04:00
John J. Aylward
bd4b180f4e Support for float to BigDecimal in optBigDecimal 2017-05-17 10:51:06 -04:00
John J. Aylward
a8d4e4734f adjustments to opt methods in reference to https://github.com/stleary/JSON-java/issues/334 2017-05-16 19:38:01 -04:00
John J. Aylward
4865f51dd5 change float double literals to be more standard as 1.0f and 1.0d respectively 2017-05-16 15:38:54 -04:00
John J. Aylward
c870094f69 Fixes spelling in comments and removes compile time warnings 2017-05-16 15:35:05 -04:00
Sean Leary
cbd8b18c4a Update README 2017-05-16 00:29:00 -05:00
Sean Leary
f12fa9ba5f Update LICENSE 2017-04-18 08:32:10 -05:00
John J. Aylward
ae1e9e2b6a fix spelling in javadoc comment 2017-04-03 11:59:36 -04:00
Sean Leary
80e2ea2a80 Merge pull request #324 from dtalex/JSONPointerOnBeans
Allow user to invoke query and optQuery ,with a JSONPointer
2017-03-27 20:04:18 -05:00
alessandro rao
2917104b53 Allow user to invoke query and optQuery ,with a JSONPointer,directly
from JSONArray or JSONObject fix JSONArray
2017-02-25 14:35:02 +01:00
alessandro rao
9e0fc5e680 Allow user to invoke query and optQuery ,with a JSONPointer,directly
from JSONArray or JSONObject
2017-02-25 13:27:50 +01:00
Sean Leary
724fb888f7 Merge pull request #317 from johnjaylward/fixLocale
make sure locale independent data is not upper/lowercased incorrectly…
2017-02-19 21:34:17 -06:00
John J. Aylward
eb806f4c14 make sure locale independent data is not upper/lowercased incorrectly. See #315 2017-02-10 10:07:28 -05:00
Sean Leary
5ff8b4cb08 Merge pull request #304 from omarzina/master
[FIX] Update README
2016-12-05 08:58:35 -06:00
joumar
5ef4f58ef1 [FIX] Update README
Fixed C&P typo
2016-12-05 11:55:24 -03:00
Sean Leary
413bb53b48 Merge pull request #288 from johnjaylward/XmlEscape
Bug fixes for XML Encoding and Decoding
2016-11-24 10:01:14 -06:00
Sean Leary
237376eca6 Merge pull request #292 from erosb/master
Provides "#" string evaluation support for JSON Pointer
2016-10-08 10:07:31 -05:00
Bence Erős
e0616a129e fixing #291 2016-10-05 14:57:42 +02:00
John J. Aylward
93ffca36c3 fixes spacing 2016-09-28 20:23:30 -04:00
John J. Aylward
e477d7002b fixes object comparison 2016-09-28 20:22:12 -04:00
John J. Aylward
fb1db9341e Changes encoding to better match the XML spec section 2.2 2016-09-28 20:15:58 -04:00
John J. Aylward
adb0478f66 properly unescape tokens in JSONML for reversability. 2016-09-22 16:23:09 -04:00
John J. Aylward
f58a0f4684 fixes code point appends to string builder 2016-09-22 16:10:49 -04:00
John J. Aylward
c11e09959c Fixes code point output when unescaping code points. XML escapes are an entire code point, not surrogate pairs like in JSON. 2016-09-22 15:40:26 -04:00
John J. Aylward
68f92eb395 Adds more javadoc. 2016-09-22 14:40:39 -04:00
John J. Aylward
34652a8706 Updates to iterate on code points instead of characters and changes the encoding to only encode control characters as defined by ISO standard. 2016-09-22 14:13:14 -04:00
John J. Aylward
a2d3b59394 Implements unicode escaping similar to JSONObject.
* Removes deprecation on XML.stringToValue(). It now provides unescaping for strings to convert XML entities back into values.
* New unescape function to handle XML entities -> value conversion.
2016-09-22 12:38:06 -04:00
Sean Leary
c24be0e4ea Merge pull request #274 from johnjaylward/NumberOutputFix
Fix for number output bug.
2016-09-09 11:42:35 -05:00
John J. Aylward
88f65c5bea Merge branch 'master' of github.com:stleary/JSON-java into NumberOutputFix
# Conflicts:
#	JSONObject.java
2016-08-26 11:32:35 -04:00
Sean Leary
ebe69df8e4 Merge pull request #271 from johnjaylward/EnumCleanup
Update enum support to be more fully featured.
2016-08-19 10:28:04 -05:00
John J. Aylward
2f2cd4dfc5 Fix for number output bug.
java.lang.Number is currently output without any validation. For all java.* Numbers, this is fine, but for custom Number implementations like Complex or Fraction, the resulting JSON output may be invalid.

For example: If a Fraction implementation defines its' toString method as `return numerator + "/" + denominator`, then the resulting JSON output would be something like this:

```json
{ "fraction" : 1/2 }
```

This is not valid JSON.

This commit verifies that the string representation of the number is close to a JSON formatted number by use of the BigDecimal constructor. If the constructor throws a NumberFormatException, then the string value is instead quoted as a string. The example above would instead output like the following:

```json
{ "fraction" : "1/2" }
```
2016-08-17 12:54:30 -04:00
John J. Aylward
349a209df3 Merge remote-tracking branch 'upstream/master' into EnumCleanup 2016-08-15 10:25:27 -04:00
John J. Aylward
7851e9b2e8 revert back changes to Number support 2016-08-15 10:24:38 -04:00
Sean Leary
7232a95c0b Update JSONObject.java
Fixed some typos committed in #249, since reverted, tracking issue is #263
2016-08-15 01:58:54 -05:00
Sean Leary
f96f505188 Update JSONArray.java
Fixed a Javadoc typo, originally fixed in #249, since reverted. This is to address issue #263
2016-08-15 01:53:08 -05:00
John J. Aylward
91107e3e82 Adds support to JSONObject wrap and write methods to explicitly handle Enums.
The new way enums are handled is to always place the actual enum in the
JSONObject/JSONArray. When writing, we always write the actual "name"
of the enum, so even with a toString override on the enum class, the
value remains consistant and compatible with the optEnum/getEnum methods.

The constructor JSONObject(Object) functions the same way as before when
passing an enum and is consistent with other "value" types. For example,
when creating a JSONObject with Long, Boolean, BigDecimal as the constructor
parameter, the value will be treated as a "bean".
2016-08-11 12:22:31 -04:00
Sean Leary
4e8e24d49d Merge pull request #259 from run2000/appendable_v2
JSONWriter uses Appendable (v2)
2016-08-10 21:55:10 -05:00
Sean Leary
f881b61c81 Update XML.java
Removed a problematic JavaDoc in the header comment to a deprecated method
2016-08-10 15:35:26 -05:00
Nicholas Cull
0c157cae75 JSONWriter uses Appendable. 2016-08-08 19:40:10 +10:00
16 changed files with 1576 additions and 486 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# ignore eclipse project files
.project
.classpath

View File

@@ -22,7 +22,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
/** /**
* This provides static methods to convert comma delimited text into a * This provides static methods to convert comma delimited text into a
@@ -70,9 +70,12 @@ public class CDL {
c = x.next(); c = x.next();
if (c == q) { if (c == q) {
//Handle escaped double-quote //Handle escaped double-quote
if(x.next() != '\"') char nextC = x.next();
{ if(nextC != '\"') {
x.back(); // if our quote was the end of the file, don't step
if(nextC > 0) {
x.back();
}
break; break;
} }
} }

View File

@@ -22,9 +22,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
import java.util.Iterator;
/** /**
* Convert a web browser cookie list string to a JSONObject and back. * Convert a web browser cookie list string to a JSONObject and back.
@@ -39,7 +37,7 @@ public class CookieList {
* The pairs are separated by ';'. The names and the values * The pairs are separated by ';'. The names and the values
* will be unescaped, possibly converting '+' and '%' sequences. * will be unescaped, possibly converting '+' and '%' sequences.
* *
* To add a cookie to a cooklist, * To add a cookie to a cookie list,
* cookielistJSONObject.put(cookieJSONObject.getString("name"), * cookielistJSONObject.put(cookieJSONObject.getString("name"),
* cookieJSONObject.getString("value")); * cookieJSONObject.getString("value"));
* @param string A cookie list string * @param string A cookie list string
@@ -69,18 +67,17 @@ public class CookieList {
*/ */
public static String toString(JSONObject jo) throws JSONException { public static String toString(JSONObject jo) throws JSONException {
boolean b = false; boolean b = false;
Iterator<String> keys = jo.keys(); final StringBuilder sb = new StringBuilder();
String string; // Don't use the new entrySet API to maintain Android support
StringBuilder sb = new StringBuilder(); for (final String key : jo.keySet()) {
while (keys.hasNext()) { final Object value = jo.opt(key);
string = keys.next(); if (!JSONObject.NULL.equals(value)) {
if (!jo.isNull(string)) {
if (b) { if (b) {
sb.append(';'); sb.append(';');
} }
sb.append(Cookie.escape(string)); sb.append(Cookie.escape(key));
sb.append("="); sb.append("=");
sb.append(Cookie.escape(jo.getString(string))); sb.append(Cookie.escape(value.toString()));
b = true; b = true;
} }
} }

View File

@@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
import java.util.Iterator; import java.util.Locale;
/** /**
* Convert an HTTP header to a JSONObject and back. * Convert an HTTP header to a JSONObject and back.
@@ -74,7 +74,7 @@ public class HTTP {
String token; String token;
token = x.nextToken(); token = x.nextToken();
if (token.toUpperCase().startsWith("HTTP")) { if (token.toUpperCase(Locale.ROOT).startsWith("HTTP")) {
// Response // Response
@@ -125,8 +125,6 @@ public class HTTP {
* information. * information.
*/ */
public static String toString(JSONObject jo) throws JSONException { public static String toString(JSONObject jo) throws JSONException {
Iterator<String> keys = jo.keys();
String string;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { if (jo.has("Status-Code") && jo.has("Reason-Phrase")) {
sb.append(jo.getString("HTTP-Version")); sb.append(jo.getString("HTTP-Version"));
@@ -146,14 +144,15 @@ public class HTTP {
throw new JSONException("Not enough material for an HTTP header."); throw new JSONException("Not enough material for an HTTP header.");
} }
sb.append(CRLF); sb.append(CRLF);
while (keys.hasNext()) { // Don't use the new entrySet API to maintain Android support
string = keys.next(); for (final String key : jo.keySet()) {
if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) && String value = jo.optString(key);
!"Reason-Phrase".equals(string) && !"Method".equals(string) && if (!"HTTP-Version".equals(key) && !"Status-Code".equals(key) &&
!"Request-URI".equals(string) && !jo.isNull(string)) { !"Reason-Phrase".equals(key) && !"Method".equals(key) &&
sb.append(string); !"Request-URI".equals(key) && !JSONObject.NULL.equals(value)) {
sb.append(key);
sb.append(": "); sb.append(": ");
sb.append(jo.getString(string)); sb.append(jo.optString(key));
sb.append(CRLF); sb.append(CRLF);
} }
} }

View File

@@ -78,7 +78,7 @@ import java.util.Map;
* </ul> * </ul>
* *
* @author JSON.org * @author JSON.org
* @version 2016-05-20 * @version 2016-08/15
*/ */
public class JSONArray implements Iterable<Object> { public class JSONArray implements Iterable<Object> {
@@ -107,7 +107,13 @@ public class JSONArray implements Iterable<Object> {
if (x.nextClean() != '[') { if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['"); throw x.syntaxError("A JSONArray text must start with '['");
} }
if (x.nextClean() != ']') {
char nextChar = x.nextClean();
if (nextChar == 0) {
// array is unclosed. No ']' found, instead EOF
throw x.syntaxError("Expected a ',' or ']'");
}
if (nextChar != ']') {
x.back(); x.back();
for (;;) { for (;;) {
if (x.nextClean() == ',') { if (x.nextClean() == ',') {
@@ -118,8 +124,16 @@ public class JSONArray implements Iterable<Object> {
this.myArrayList.add(x.nextValue()); this.myArrayList.add(x.nextValue());
} }
switch (x.nextClean()) { switch (x.nextClean()) {
case 0:
// array is unclosed. No ']' found, instead EOF
throw x.syntaxError("Expected a ',' or ']'");
case ',': case ',':
if (x.nextClean() == ']') { nextChar = x.nextClean();
if (nextChar == 0) {
// array is unclosed. No ']' found, instead EOF
throw x.syntaxError("Expected a ',' or ']'");
}
if (nextChar == ']') {
return; return;
} }
x.back(); x.back();
@@ -154,8 +168,10 @@ public class JSONArray implements Iterable<Object> {
* A Collection. * A Collection.
*/ */
public JSONArray(Collection<?> collection) { public JSONArray(Collection<?> collection) {
this.myArrayList = new ArrayList<Object>(); if (collection == null) {
if (collection != null) { this.myArrayList = new ArrayList<Object>();
} else {
this.myArrayList = new ArrayList<Object>(collection.size());
for (Object o: collection){ for (Object o: collection){
this.myArrayList.add(JSONObject.wrap(o)); this.myArrayList.add(JSONObject.wrap(o));
} }
@@ -172,6 +188,7 @@ public class JSONArray implements Iterable<Object> {
this(); this();
if (array.getClass().isArray()) { if (array.getClass().isArray()) {
int length = Array.getLength(array); int length = Array.getLength(array);
this.myArrayList.ensureCapacity(length);
for (int i = 0; i < length; i += 1) { for (int i = 0; i < length; i += 1) {
this.put(JSONObject.wrap(Array.get(array, i))); this.put(JSONObject.wrap(Array.get(array, i)));
} }
@@ -183,7 +200,7 @@ public class JSONArray implements Iterable<Object> {
@Override @Override
public Iterator<Object> iterator() { public Iterator<Object> iterator() {
return myArrayList.iterator(); return this.myArrayList.iterator();
} }
/** /**
@@ -244,7 +261,50 @@ public class JSONArray implements Iterable<Object> {
return object instanceof Number ? ((Number) object).doubleValue() return object instanceof Number ? ((Number) object).doubleValue()
: Double.parseDouble((String) object); : Double.parseDouble((String) object);
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + "] is not a number."); throw new JSONException("JSONArray[" + index + "] is not a number.", e);
}
}
/**
* Get the float value associated with a key.
*
* @param index
* The index must be between 0 and length() - 1.
* @return The numeric value.
* @throws JSONException
* if the key is not found or if the value is not a Number
* object and cannot be converted to a number.
*/
public float getFloat(int index) throws JSONException {
Object object = this.get(index);
try {
return object instanceof Number ? ((Number) object).floatValue()
: Float.parseFloat(object.toString());
} catch (Exception e) {
throw new JSONException("JSONArray[" + index
+ "] is not a number.", e);
}
}
/**
* Get the Number value associated with a key.
*
* @param index
* The index must be between 0 and length() - 1.
* @return The numeric value.
* @throws JSONException
* if the key is not found or if the value is not a Number
* object and cannot be converted to a number.
*/
public Number getNumber(int index) throws JSONException {
Object object = this.get(index);
try {
if (object instanceof Number) {
return (Number)object;
}
return JSONObject.stringToNumber(object.toString());
} catch (Exception e) {
throw new JSONException("JSONArray[" + index + "] is not a number.", e);
} }
} }
@@ -266,9 +326,8 @@ public class JSONArray implements Iterable<Object> {
// JSONException should really take a throwable argument. // JSONException should really take a throwable argument.
// If it did, I would re-implement this with the Enum.valueOf // If it did, I would re-implement this with the Enum.valueOf
// method and place any thrown exception in the JSONException // method and place any thrown exception in the JSONException
throw new JSONException("JSONObject[" + JSONObject.quote(Integer.toString(index)) throw new JSONException("JSONArray[" + index + "] is not an enum of type "
+ "] is not an enum of type " + JSONObject.quote(clazz.getSimpleName()) + JSONObject.quote(clazz.getSimpleName()) + ".");
+ ".");
} }
return val; return val;
} }
@@ -289,7 +348,7 @@ public class JSONArray implements Iterable<Object> {
return new BigDecimal(object.toString()); return new BigDecimal(object.toString());
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index +
"] could not convert to BigDecimal."); "] could not convert to BigDecimal.", e);
} }
} }
@@ -309,7 +368,7 @@ public class JSONArray implements Iterable<Object> {
return new BigInteger(object.toString()); return new BigInteger(object.toString());
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + throw new JSONException("JSONArray[" + index +
"] could not convert to BigInteger."); "] could not convert to BigInteger.", e);
} }
} }
@@ -328,7 +387,7 @@ public class JSONArray implements Iterable<Object> {
return object instanceof Number ? ((Number) object).intValue() return object instanceof Number ? ((Number) object).intValue()
: Integer.parseInt((String) object); : Integer.parseInt((String) object);
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + "] is not a number."); throw new JSONException("JSONArray[" + index + "] is not a number.", e);
} }
} }
@@ -384,7 +443,7 @@ public class JSONArray implements Iterable<Object> {
return object instanceof Number ? ((Number) object).longValue() return object instanceof Number ? ((Number) object).longValue()
: Long.parseLong((String) object); : Long.parseLong((String) object);
} catch (Exception e) { } catch (Exception e) {
throw new JSONException("JSONArray[" + index + "] is not a number."); throw new JSONException("JSONArray[" + index + "] is not a number.", e);
} }
} }
@@ -453,7 +512,7 @@ public class JSONArray implements Iterable<Object> {
* Get the optional object value associated with an index. * Get the optional object value associated with an index.
* *
* @param index * @param index
* The index must be between 0 and length() - 1. * The index must be between 0 and length() - 1. If not, null is returned.
* @return An object value, or null if there is no object at that index. * @return An object value, or null if there is no object at that index.
*/ */
public Object opt(int index) { public Object opt(int index) {
@@ -518,11 +577,63 @@ public class JSONArray implements Iterable<Object> {
* @return The value. * @return The value.
*/ */
public double optDouble(int index, double defaultValue) { public double optDouble(int index, double defaultValue) {
try { Object val = this.opt(index);
return this.getDouble(index); if (JSONObject.NULL.equals(val)) {
} catch (Exception e) {
return defaultValue; return defaultValue;
} }
if (val instanceof Number){
return ((Number) val).doubleValue();
}
if (val instanceof String) {
try {
return Double.parseDouble((String) val);
} catch (Exception e) {
return defaultValue;
}
}
return defaultValue;
}
/**
* Get the optional float value associated with an index. NaN is returned
* if there is no value for the index, or if the value is not a number and
* cannot be converted to a number.
*
* @param index
* The index must be between 0 and length() - 1.
* @return The value.
*/
public float optFloat(int index) {
return this.optFloat(index, Float.NaN);
}
/**
* Get the optional float value associated with an index. The defaultValue
* is returned if there is no value for the index, or if the value is not a
* number and cannot be converted to a number.
*
* @param index
* subscript
* @param defaultValue
* The default value.
* @return The value.
*/
public float optFloat(int index, float defaultValue) {
Object val = this.opt(index);
if (JSONObject.NULL.equals(val)) {
return defaultValue;
}
if (val instanceof Number){
return ((Number) val).floatValue();
}
if (val instanceof String) {
try {
return Float.parseFloat((String) val);
} catch (Exception e) {
return defaultValue;
}
}
return defaultValue;
} }
/** /**
@@ -550,11 +661,22 @@ public class JSONArray implements Iterable<Object> {
* @return The value. * @return The value.
*/ */
public int optInt(int index, int defaultValue) { public int optInt(int index, int defaultValue) {
try { Object val = this.opt(index);
return this.getInt(index); if (JSONObject.NULL.equals(val)) {
} catch (Exception e) {
return defaultValue; return defaultValue;
} }
if (val instanceof Number){
return ((Number) val).intValue();
}
if (val instanceof String) {
try {
return new BigDecimal(val.toString()).intValue();
} catch (Exception e) {
return defaultValue;
}
}
return defaultValue;
} }
/** /**
@@ -615,8 +737,29 @@ public class JSONArray implements Iterable<Object> {
* @return The value. * @return The value.
*/ */
public BigInteger optBigInteger(int index, BigInteger defaultValue) { public BigInteger optBigInteger(int index, BigInteger defaultValue) {
Object val = this.opt(index);
if (JSONObject.NULL.equals(val)) {
return defaultValue;
}
if (val instanceof BigInteger){
return (BigInteger) val;
}
if (val instanceof BigDecimal){
return ((BigDecimal) val).toBigInteger();
}
if (val instanceof Double || val instanceof Float){
return new BigDecimal(((Number) val).doubleValue()).toBigInteger();
}
if (val instanceof Long || val instanceof Integer
|| val instanceof Short || val instanceof Byte){
return BigInteger.valueOf(((Number) val).longValue());
}
try { try {
return this.getBigInteger(index); final String valStr = val.toString();
if(JSONObject.isDecimalNotation(valStr)) {
return new BigDecimal(valStr).toBigInteger();
}
return new BigInteger(valStr);
} catch (Exception e) { } catch (Exception e) {
return defaultValue; return defaultValue;
} }
@@ -634,8 +777,25 @@ public class JSONArray implements Iterable<Object> {
* @return The value. * @return The value.
*/ */
public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) {
Object val = this.opt(index);
if (JSONObject.NULL.equals(val)) {
return defaultValue;
}
if (val instanceof BigDecimal){
return (BigDecimal) val;
}
if (val instanceof BigInteger){
return new BigDecimal((BigInteger) val);
}
if (val instanceof Double || val instanceof Float){
return new BigDecimal(((Number) val).doubleValue());
}
if (val instanceof Long || val instanceof Integer
|| val instanceof Short || val instanceof Byte){
return new BigDecimal(((Number) val).longValue());
}
try { try {
return this.getBigDecimal(index); return new BigDecimal(val.toString());
} catch (Exception e) { } catch (Exception e) {
return defaultValue; return defaultValue;
} }
@@ -693,17 +853,73 @@ public class JSONArray implements Iterable<Object> {
* @return The value. * @return The value.
*/ */
public long optLong(int index, long defaultValue) { public long optLong(int index, long defaultValue) {
try { Object val = this.opt(index);
return this.getLong(index); if (JSONObject.NULL.equals(val)) {
} catch (Exception e) {
return defaultValue; return defaultValue;
} }
if (val instanceof Number){
return ((Number) val).longValue();
}
if (val instanceof String) {
try {
return new BigDecimal(val.toString()).longValue();
} catch (Exception e) {
return defaultValue;
}
}
return defaultValue;
}
/**
* Get an optional {@link Number} value associated with a key, or <code>null</code>
* if there is no such key or if the value is not a number. If the value is a string,
* an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method
* would be used in cases where type coercion of the number value is unwanted.
*
* @param index
* The index must be between 0 and length() - 1.
* @return An object which is the value.
*/
public Number optNumber(int index) {
return this.optNumber(index, null);
}
/**
* Get an optional {@link Number} value associated with a key, or the default if there
* is no such key or if the value is not a number. If the value is a string,
* an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method
* would be used in cases where type coercion of the number value is unwanted.
*
* @param index
* The index must be between 0 and length() - 1.
* @param defaultValue
* The default.
* @return An object which is the value.
*/
public Number optNumber(int index, Number defaultValue) {
Object val = this.opt(index);
if (JSONObject.NULL.equals(val)) {
return defaultValue;
}
if (val instanceof Number){
return (Number) val;
}
if (val instanceof String) {
try {
return JSONObject.stringToNumber((String) val);
} catch (Exception e) {
return defaultValue;
}
}
return defaultValue;
} }
/** /**
* Get the optional string value associated with an index. It returns an * Get the optional string value associated with an index. It returns an
* empty string if there is no value at that index. If the value is not a * empty string if there is no value at that index. If the value is not a
* string and is not null, then it is coverted to a string. * string and is not null, then it is converted to a string.
* *
* @param index * @param index
* The index must be between 0 and length() - 1. * The index must be between 0 and length() - 1.
@@ -951,7 +1167,13 @@ public class JSONArray implements Iterable<Object> {
} }
if (index < this.length()) { if (index < this.length()) {
this.myArrayList.set(index, value); this.myArrayList.set(index, value);
} else if(index == this.length()){
// simple append
this.put(value);
} else { } else {
// if we are inserting past the length, we want to grow the array all at once
// instead of incrementally.
this.myArrayList.ensureCapacity(index + 1);
while (index != this.length()) { while (index != this.length()) {
this.put(JSONObject.NULL); this.put(JSONObject.NULL);
} }
@@ -961,7 +1183,7 @@ public class JSONArray implements Iterable<Object> {
} }
/** /**
* Creates a JSONPointer using an intialization string and tries to * Creates a JSONPointer using an initialization string and tries to
* match it to an item within this JSONArray. For example, given a * match it to an item within this JSONArray. For example, given a
* JSONArray initialized with this document: * JSONArray initialized with this document:
* <pre> * <pre>
@@ -980,7 +1202,30 @@ public class JSONArray implements Iterable<Object> {
* @return the item matched by the JSONPointer, otherwise null * @return the item matched by the JSONPointer, otherwise null
*/ */
public Object query(String jsonPointer) { public Object query(String jsonPointer) {
return new JSONPointer(jsonPointer).queryFrom(this); return query(new JSONPointer(jsonPointer));
}
/**
* Uses a uaer initialized JSONPointer and tries to
* match it to an item whithin this JSONArray. For example, given a
* JSONArray initialized with this document:
* <pre>
* [
* {"b":"c"}
* ]
* </pre>
* and this JSONPointer:
* <pre>
* "/0/b"
* </pre>
* Then this method will return the String "c"
* A JSONPointerException may be thrown from code called by this method.
*
* @param jsonPointer string that can be used to create a JSONPointer
* @return the item matched by the JSONPointer, otherwise null
*/
public Object query(JSONPointer jsonPointer) {
return jsonPointer.queryFrom(this);
} }
/** /**
@@ -992,9 +1237,20 @@ public class JSONArray implements Iterable<Object> {
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
*/ */
public Object optQuery(String jsonPointer) { public Object optQuery(String jsonPointer) {
JSONPointer pointer = new JSONPointer(jsonPointer); return optQuery(new JSONPointer(jsonPointer));
}
/**
* Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key.
*
* @param jsonPointer The JSON pointer
* @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
*/
public Object optQuery(JSONPointer jsonPointer) {
try { try {
return pointer.queryFrom(this); return jsonPointer.queryFrom(this);
} catch (JSONPointerException e) { } catch (JSONPointerException e) {
return null; return null;
} }
@@ -1030,8 +1286,14 @@ public class JSONArray implements Iterable<Object> {
return false; return false;
} }
for (int i = 0; i < len; i += 1) { for (int i = 0; i < len; i += 1) {
Object valueThis = this.get(i); Object valueThis = this.myArrayList.get(i);
Object valueOther = ((JSONArray)other).get(i); Object valueOther = ((JSONArray)other).myArrayList.get(i);
if(valueThis == valueOther) {
continue;
}
if(valueThis == null) {
return false;
}
if (valueThis instanceof JSONObject) { if (valueThis instanceof JSONObject) {
if (!((JSONObject)valueThis).similar(valueOther)) { if (!((JSONObject)valueThis).similar(valueOther)) {
return false; return false;
@@ -1063,7 +1325,7 @@ public class JSONArray implements Iterable<Object> {
if (names == null || names.length() == 0 || this.length() == 0) { if (names == null || names.length() == 0 || this.length() == 0) {
return null; return null;
} }
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject(names.length());
for (int i = 0; i < names.length(); i += 1) { for (int i = 0; i < names.length(); i += 1) {
jo.put(names.getString(i), this.opt(i)); jo.put(names.getString(i), this.opt(i));
} }
@@ -1075,12 +1337,14 @@ public class JSONArray implements Iterable<Object> {
* whitespace is added. If it is not possible to produce a syntactically * whitespace is added. If it is not possible to produce a syntactically
* correct JSON text then null will be returned instead. This could occur if * correct JSON text then null will be returned instead. This could occur if
* the array contains an invalid number. * the array contains an invalid number.
* <p> * <p><b>
* Warning: This method assumes that the data structure is acyclical. * Warning: This method assumes that the data structure is acyclical.
* </b>
* *
* @return a printable, displayable, transmittable representation of the * @return a printable, displayable, transmittable representation of the
* array. * array.
*/ */
@Override
public String toString() { public String toString() {
try { try {
return this.toString(0); return this.toString(0);
@@ -1090,9 +1354,24 @@ public class JSONArray implements Iterable<Object> {
} }
/** /**
* Make a prettyprinted JSON text of this JSONArray. Warning: This method * Make a pretty-printed JSON text of this JSONArray.
* assumes that the data structure is acyclical. *
* * <p>If <code>indentFactor > 0</code> and the {@link JSONArray} has only
* one element, then the array will be output on a single line:
* <pre>{@code [1]}</pre>
*
* <p>If an array has 2 or more elements, then it will be output across
* multiple lines: <pre>{@code
* [
* 1,
* "value 2",
* 3
* ]
* }</pre>
* <p><b>
* Warning: This method assumes that the data structure is acyclical.
* </b>
*
* @param indentFactor * @param indentFactor
* The number of spaces to add to each level of indentation. * The number of spaces to add to each level of indentation.
* @return a printable, displayable, transmittable representation of the * @return a printable, displayable, transmittable representation of the
@@ -1111,8 +1390,9 @@ public class JSONArray implements Iterable<Object> {
/** /**
* Write the contents of the JSONArray as JSON text to a writer. For * Write the contents of the JSONArray as JSON text to a writer. For
* compactness, no whitespace is added. * compactness, no whitespace is added.
* <p> * <p><b>
* Warning: This method assumes that the data structure is acyclical. * Warning: This method assumes that the data structure is acyclical.
*</b>
* *
* @return The writer. * @return The writer.
* @throws JSONException * @throws JSONException
@@ -1122,17 +1402,30 @@ public class JSONArray implements Iterable<Object> {
} }
/** /**
* Write the contents of the JSONArray as JSON text to a writer. For * Write the contents of the JSONArray as JSON text to a writer.
* compactness, no whitespace is added. *
* <p> * <p>If <code>indentFactor > 0</code> and the {@link JSONArray} has only
* one element, then the array will be output on a single line:
* <pre>{@code [1]}</pre>
*
* <p>If an array has 2 or more elements, then it will be output across
* multiple lines: <pre>{@code
* [
* 1,
* "value 2",
* 3
* ]
* }</pre>
* <p><b>
* Warning: This method assumes that the data structure is acyclical. * Warning: This method assumes that the data structure is acyclical.
* </b>
* *
* @param writer * @param writer
* Writes the serialized JSON * Writes the serialized JSON
* @param indentFactor * @param indentFactor
* The number of spaces to add to each level of indentation. * The number of spaces to add to each level of indentation.
* @param indent * @param indent
* The indention of the top level. * The indentation of the top level.
* @return The writer. * @return The writer.
* @throws JSONException * @throws JSONException
*/ */
@@ -1144,8 +1437,12 @@ public class JSONArray implements Iterable<Object> {
writer.write('['); writer.write('[');
if (length == 1) { if (length == 1) {
JSONObject.writeValue(writer, this.myArrayList.get(0), try {
indentFactor, indent); JSONObject.writeValue(writer, this.myArrayList.get(0),
indentFactor, indent);
} catch (Exception e) {
throw new JSONException("Unable to write JSONArray value at index: 0", e);
}
} else if (length != 0) { } else if (length != 0) {
final int newindent = indent + indentFactor; final int newindent = indent + indentFactor;
@@ -1157,8 +1454,12 @@ public class JSONArray implements Iterable<Object> {
writer.write('\n'); writer.write('\n');
} }
JSONObject.indent(writer, newindent); JSONObject.indent(writer, newindent);
JSONObject.writeValue(writer, this.myArrayList.get(i), try {
indentFactor, newindent); JSONObject.writeValue(writer, this.myArrayList.get(i),
indentFactor, newindent);
} catch (Exception e) {
throw new JSONException("Unable to write JSONArray value at index: " + i, e);
}
commanate = true; commanate = true;
} }
if (indentFactor > 0) { if (indentFactor > 0) {

View File

@@ -24,9 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
import java.util.Iterator;
/** /**
* This provides static methods to convert an XML text into a JSONArray or * This provides static methods to convert an XML text into a JSONArray or
* JSONObject, and to covert a JSONArray or JSONObject into an XML text using * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
@@ -42,7 +39,7 @@ public class JSONML {
* @param arrayForm true if array form, false if object form. * @param arrayForm true if array form, false if object form.
* @param ja The JSONArray that is containing the current tag or null * @param ja The JSONArray that is containing the current tag or null
* if we are at the outermost level. * if we are at the outermost level.
* @param keepStrings Don't type-convert text nodes and attibute values * @param keepStrings Don't type-convert text nodes and attribute values
* @return A JSONArray if the value is the outermost tag, otherwise null. * @return A JSONArray if the value is the outermost tag, otherwise null.
* @throws JSONException * @throws JSONException
*/ */
@@ -175,7 +172,7 @@ public class JSONML {
if (!(token instanceof String)) { if (!(token instanceof String)) {
throw x.syntaxError("Missing value"); throw x.syntaxError("Missing value");
} }
newjo.accumulate(attribute, keepStrings ? token :JSONObject.stringToValue((String)token)); newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token));
token = null; token = null;
} else { } else {
newjo.accumulate(attribute, ""); newjo.accumulate(attribute, "");
@@ -226,7 +223,7 @@ public class JSONML {
} else { } else {
if (ja != null) { if (ja != null) {
ja.put(token instanceof String ja.put(token instanceof String
? keepStrings ? token :JSONObject.stringToValue((String)token) ? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token)
: token); : token);
} }
} }
@@ -397,13 +394,10 @@ public class JSONML {
public static String toString(JSONArray ja) throws JSONException { public static String toString(JSONArray ja) throws JSONException {
int i; int i;
JSONObject jo; JSONObject jo;
String key;
Iterator<String> keys;
int length; int length;
Object object; Object object;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String tagName; String tagName;
String value;
// Emit <tagName // Emit <tagName
@@ -420,17 +414,16 @@ public class JSONML {
// Emit the attributes // Emit the attributes
keys = jo.keys(); // Don't use the new entrySet API to maintain Android support
while (keys.hasNext()) { for (final String key : jo.keySet()) {
key = keys.next(); final Object value = jo.opt(key);
XML.noSpace(key); XML.noSpace(key);
value = jo.optString(key);
if (value != null) { if (value != null) {
sb.append(' '); sb.append(' ');
sb.append(XML.escape(key)); sb.append(XML.escape(key));
sb.append('='); sb.append('=');
sb.append('"'); sb.append('"');
sb.append(XML.escape(value)); sb.append(XML.escape(value.toString()));
sb.append('"'); sb.append('"');
} }
} }
@@ -482,12 +475,10 @@ public class JSONML {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int i; int i;
JSONArray ja; JSONArray ja;
String key;
Iterator<String> keys;
int length; int length;
Object object; Object object;
String tagName; String tagName;
String value; Object value;
//Emit <tagName //Emit <tagName
@@ -502,18 +493,17 @@ public class JSONML {
//Emit the attributes //Emit the attributes
keys = jo.keys(); // Don't use the new entrySet API to maintain Android support
while (keys.hasNext()) { for (final String key : jo.keySet()) {
key = keys.next();
if (!"tagName".equals(key) && !"childNodes".equals(key)) { if (!"tagName".equals(key) && !"childNodes".equals(key)) {
XML.noSpace(key); XML.noSpace(key);
value = jo.optString(key); value = jo.opt(key);
if (value != null) { if (value != null) {
sb.append(' '); sb.append(' ');
sb.append(XML.escape(key)); sb.append(XML.escape(key));
sb.append('='); sb.append('=');
sb.append('"'); sb.append('"');
sb.append(XML.escape(value)); sb.append(XML.escape(value.toString()));
sb.append('"'); sb.append('"');
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,9 @@ import static java.lang.String.format;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/* /*
Copyright (c) 2002 JSON.org Copyright (c) 2002 JSON.org
@@ -68,11 +70,11 @@ public class JSONPointer {
* {@link #append(String)} method calls. * {@link #append(String)} method calls.
*/ */
public JSONPointer build() { public JSONPointer build() {
return new JSONPointer(refTokens); return new JSONPointer(this.refTokens);
} }
/** /**
* Adds an arbitary token to the list of reference tokens. It can be any non-null value. * Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
* *
* Unlike in the case of JSON string or URI fragment representation of JSON pointers, the * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
* argument of this method MUST NOT be escaped. If you want to query the property called * argument of this method MUST NOT be escaped. If you want to query the property called
@@ -87,7 +89,7 @@ public class JSONPointer {
if (token == null) { if (token == null) {
throw new NullPointerException("token cannot be null"); throw new NullPointerException("token cannot be null");
} }
refTokens.add(token); this.refTokens.add(token);
return this; return this;
} }
@@ -99,7 +101,7 @@ public class JSONPointer {
* @return {@code this} * @return {@code this}
*/ */
public Builder append(int arrayIndex) { public Builder append(int arrayIndex) {
refTokens.add(String.valueOf(arrayIndex)); this.refTokens.add(String.valueOf(arrayIndex));
return this; return this;
} }
} }
@@ -134,29 +136,30 @@ public class JSONPointer {
* @param pointer the JSON String or URI Fragment representation of the JSON pointer. * @param pointer the JSON String or URI Fragment representation of the JSON pointer.
* @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer
*/ */
public JSONPointer(String pointer) { public JSONPointer(final String pointer) {
if (pointer == null) { if (pointer == null) {
throw new NullPointerException("pointer cannot be null"); throw new NullPointerException("pointer cannot be null");
} }
if (pointer.isEmpty()) { if (pointer.isEmpty() || pointer.equals("#")) {
refTokens = Collections.emptyList(); this.refTokens = Collections.emptyList();
return; return;
} }
String refs;
if (pointer.startsWith("#/")) { if (pointer.startsWith("#/")) {
pointer = pointer.substring(2); refs = pointer.substring(2);
try { try {
pointer = URLDecoder.decode(pointer, ENCODING); refs = URLDecoder.decode(refs, ENCODING);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} else if (pointer.startsWith("/")) { } else if (pointer.startsWith("/")) {
pointer = pointer.substring(1); refs = pointer.substring(1);
} else { } else {
throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
} }
refTokens = new ArrayList<String>(); this.refTokens = new ArrayList<String>();
for (String token : pointer.split("/")) { for (String token : refs.split("/")) {
refTokens.add(unescape(token)); this.refTokens.add(unescape(token));
} }
} }
@@ -180,12 +183,12 @@ public class JSONPointer {
* @return the result of the evaluation * @return the result of the evaluation
* @throws JSONPointerException if an error occurs during evaluation * @throws JSONPointerException if an error occurs during evaluation
*/ */
public Object queryFrom(Object document) { public Object queryFrom(Object document) throws JSONPointerException {
if (refTokens.isEmpty()) { if (this.refTokens.isEmpty()) {
return document; return document;
} }
Object current = document; Object current = document;
for (String token : refTokens) { for (String token : this.refTokens) {
if (current instanceof JSONObject) { if (current instanceof JSONObject) {
current = ((JSONObject) current).opt(unescape(token)); current = ((JSONObject) current).opt(unescape(token));
} else if (current instanceof JSONArray) { } else if (current instanceof JSONArray) {
@@ -204,9 +207,9 @@ public class JSONPointer {
* @param current the JSONArray to be evaluated * @param current the JSONArray to be evaluated
* @param indexToken the array index in string form * @param indexToken the array index in string form
* @return the matched object. If no matching item is found a * @return the matched object. If no matching item is found a
* JSONPointerException is thrown * @throws JSONPointerException is thrown if the index is out of bounds
*/ */
private Object readByIndexToken(Object current, String indexToken) { private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
try { try {
int index = Integer.parseInt(indexToken); int index = Integer.parseInt(indexToken);
JSONArray currentArr = (JSONArray) current; JSONArray currentArr = (JSONArray) current;
@@ -214,7 +217,11 @@ public class JSONPointer {
throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index,
currentArr.length())); currentArr.length()));
} }
return currentArr.get(index); try {
return currentArr.get(index);
} catch (JSONException e) {
throw new JSONPointerException("Error reading value at index position " + index, e);
}
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JSONPointerException(format("%s is not an array index", indexToken), e); throw new JSONPointerException(format("%s is not an array index", indexToken), e);
} }
@@ -227,7 +234,7 @@ public class JSONPointer {
@Override @Override
public String toString() { public String toString() {
StringBuilder rval = new StringBuilder(""); StringBuilder rval = new StringBuilder("");
for (String token: refTokens) { for (String token: this.refTokens) {
rval.append('/').append(escape(token)); rval.append('/').append(escape(token));
} }
return rval.toString(); return rval.toString();
@@ -255,7 +262,7 @@ public class JSONPointer {
public String toURIFragment() { public String toURIFragment() {
try { try {
StringBuilder rval = new StringBuilder("#"); StringBuilder rval = new StringBuilder("#");
for (String token : refTokens) { for (String token : this.refTokens) {
rval.append('/').append(URLEncoder.encode(token, ENCODING)); rval.append('/').append(URLEncoder.encode(token, ENCODING));
} }
return rval.toString(); return rval.toString();

View File

@@ -72,6 +72,7 @@ public class JSONStringer extends JSONWriter {
* <code>endArray</code>). * <code>endArray</code>).
* @return The JSON text. * @return The JSON text.
*/ */
@Override
public String toString() { public String toString() {
return this.mode == 'd' ? this.writer.toString() : null; return this.mode == 'd' ? this.writer.toString() : null;
} }

View File

@@ -29,7 +29,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
/** /**
* A JSONTokener takes a source string and extracts characters and tokens from * A JSONTokener takes a source string and extracts characters and tokens from
@@ -39,36 +39,45 @@ SOFTWARE.
* @version 2014-05-03 * @version 2014-05-03
*/ */
public class JSONTokener { public class JSONTokener {
/** current read character position on the current line. */
private long character; private long character;
/** flag to indicate if the end of the input has been found. */
private boolean eof; private boolean eof;
private long index; /** current read index of the input. */
private long line; private long index;
private char previous; /** current line of the input. */
private Reader reader; private long line;
/** previous character read from the input. */
private char previous;
/** Reader for the input. */
private final Reader reader;
/** flag to indicate that a previous character was requested. */
private boolean usePrevious; private boolean usePrevious;
/** the number of characters read in the previous line. */
private long characterPreviousLine;
/** /**
* Construct a JSONTokener from a Reader. * Construct a JSONTokener from a Reader. The caller must close the Reader.
* *
* @param reader A reader. * @param reader A reader.
*/ */
public JSONTokener(Reader reader) { public JSONTokener(Reader reader) {
this.reader = reader.markSupported() this.reader = reader.markSupported()
? reader ? reader
: new BufferedReader(reader); : new BufferedReader(reader);
this.eof = false; this.eof = false;
this.usePrevious = false; this.usePrevious = false;
this.previous = 0; this.previous = 0;
this.index = 0; this.index = 0;
this.character = 1; this.character = 1;
this.characterPreviousLine = 0;
this.line = 1; this.line = 1;
} }
/** /**
* Construct a JSONTokener from an InputStream. * Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param inputStream The source. * @param inputStream The source.
*/ */
public JSONTokener(InputStream inputStream) { public JSONTokener(InputStream inputStream) {
@@ -97,12 +106,23 @@ public class JSONTokener {
if (this.usePrevious || this.index <= 0) { if (this.usePrevious || this.index <= 0) {
throw new JSONException("Stepping back two steps is not supported"); throw new JSONException("Stepping back two steps is not supported");
} }
this.index -= 1; this.decrementIndexes();
this.character -= 1;
this.usePrevious = true; this.usePrevious = true;
this.eof = false; this.eof = false;
} }
/**
* Decrements the indexes for the {@link #back()} method based on the previous character read.
*/
private void decrementIndexes() {
this.index--;
if(this.previous=='\r' || this.previous == '\n') {
this.line--;
this.character=this.characterPreviousLine ;
} else if(this.character > 0){
this.character--;
}
}
/** /**
* Get the hex value of a character (base16). * Get the hex value of a character (base16).
@@ -124,6 +144,8 @@ public class JSONTokener {
} }
/** /**
* Checks if the end of the input has been reached.
*
* @return true if at the end of the file and we didn't step back * @return true if at the end of the file and we didn't step back
*/ */
public boolean end() { public boolean end() {
@@ -139,11 +161,24 @@ public class JSONTokener {
* or backward while checking for more data. * or backward while checking for more data.
*/ */
public boolean more() throws JSONException { public boolean more() throws JSONException {
this.next(); if(this.usePrevious) {
if (this.end()) { return true;
return false; }
try {
this.reader.mark(1);
} catch (IOException e) {
throw new JSONException("Unable to preserve stream position", e);
}
try {
// -1 is EOF, but next() can not consume the null character '\0'
if(this.reader.read() <= 0) {
this.eof = true;
return false;
}
this.reader.reset();
} catch (IOException e) {
throw new JSONException("Unable to read the next character from the stream", e);
} }
this.back();
return true; return true;
} }
@@ -165,26 +200,39 @@ public class JSONTokener {
} catch (IOException exception) { } catch (IOException exception) {
throw new JSONException(exception); throw new JSONException(exception);
} }
if (c <= 0) { // End of stream
this.eof = true;
c = 0;
}
} }
this.index += 1; if (c <= 0) { // End of stream
if (this.previous == '\r') { this.eof = true;
this.line += 1; return 0;
this.character = c == '\n' ? 0 : 1;
} else if (c == '\n') {
this.line += 1;
this.character = 0;
} else {
this.character += 1;
} }
this.incrementIndexes(c);
this.previous = (char) c; this.previous = (char) c;
return this.previous; return this.previous;
} }
/**
* Increments the internal indexes according to the previous character
* read and the character passed as the current character.
* @param c the current character read.
*/
private void incrementIndexes(int c) {
if(c > 0) {
this.index++;
if(c=='\r') {
this.line++;
this.characterPreviousLine = this.character;
this.character=0;
}else if (c=='\n') {
if(this.previous != '\r') {
this.line++;
this.characterPreviousLine = this.character;
}
this.character=0;
} else {
this.character++;
}
}
}
/** /**
* Consume the next character, and check that it matches a specified * Consume the next character, and check that it matches a specified
@@ -196,8 +244,11 @@ public class JSONTokener {
public char next(char c) throws JSONException { public char next(char c) throws JSONException {
char n = this.next(); char n = this.next();
if (n != c) { if (n != c) {
throw this.syntaxError("Expected '" + c + "' and instead saw '" + if(n > 0) {
n + "'"); throw this.syntaxError("Expected '" + c + "' and instead saw '" +
n + "'");
}
throw this.syntaxError("Expected '" + c + "' and instead saw ''");
} }
return n; return n;
} }
@@ -212,23 +263,23 @@ public class JSONTokener {
* Substring bounds error if there are not * Substring bounds error if there are not
* n characters remaining in the source string. * n characters remaining in the source string.
*/ */
public String next(int n) throws JSONException { public String next(int n) throws JSONException {
if (n == 0) { if (n == 0) {
return ""; return "";
} }
char[] chars = new char[n]; char[] chars = new char[n];
int pos = 0; int pos = 0;
while (pos < n) { while (pos < n) {
chars[pos] = this.next(); chars[pos] = this.next();
if (this.end()) { if (this.end()) {
throw this.syntaxError("Substring bounds error"); throw this.syntaxError("Substring bounds error");
} }
pos += 1; pos += 1;
} }
return new String(chars); return new String(chars);
} }
/** /**
@@ -372,15 +423,15 @@ public class JSONTokener {
String string; String string;
switch (c) { switch (c) {
case '"': case '"':
case '\'': case '\'':
return this.nextString(c); return this.nextString(c);
case '{': case '{':
this.back(); this.back();
return new JSONObject(this); return new JSONObject(this);
case '[': case '[':
this.back(); this.back();
return new JSONArray(this); return new JSONArray(this);
} }
/* /*
@@ -426,13 +477,17 @@ public class JSONTokener {
do { do {
c = this.next(); c = this.next();
if (c == 0) { if (c == 0) {
// in some readers, reset() may throw an exception if
// the remaining portion of the input is greater than
// the mark size (1,000,000 above).
this.reader.reset(); this.reader.reset();
this.index = startIndex; this.index = startIndex;
this.character = startCharacter; this.character = startCharacter;
this.line = startLine; this.line = startLine;
return c; return 0;
} }
} while (c != to); } while (c != to);
this.reader.mark(1);
} catch (IOException exception) { } catch (IOException exception) {
throw new JSONException(exception); throw new JSONException(exception);
} }
@@ -440,7 +495,6 @@ public class JSONTokener {
return c; return c;
} }
/** /**
* Make a JSONException to signal a syntax error. * Make a JSONException to signal a syntax error.
* *
@@ -470,6 +524,6 @@ public class JSONTokener {
@Override @Override
public String toString() { public String toString() {
return " at " + this.index + " [character " + this.character + " line " + return " at " + this.index + " [character " + this.character + " line " +
this.line + "]"; this.line + "]";
} }
} }

View File

@@ -1,7 +1,9 @@
package org.json; package org.json;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.math.BigDecimal;
import java.util.Collection;
import java.util.Map;
/* /*
Copyright (c) 2006 JSON.org Copyright (c) 2006 JSON.org
@@ -50,11 +52,11 @@ SOFTWARE.
* <p> * <p>
* The first method called must be <code>array</code> or <code>object</code>. * The first method called must be <code>array</code> or <code>object</code>.
* There are no methods for adding commas or colons. JSONWriter adds them for * There are no methods for adding commas or colons. JSONWriter adds them for
* you. Objects and arrays can be nested up to 20 levels deep. * you. Objects and arrays can be nested up to 200 levels deep.
* <p> * <p>
* This can sometimes be easier than using a JSONObject to build a string. * This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org * @author JSON.org
* @version 2015-12-09 * @version 2016-08-08
*/ */
public class JSONWriter { public class JSONWriter {
private static final int maxdepth = 200; private static final int maxdepth = 200;
@@ -88,12 +90,12 @@ public class JSONWriter {
/** /**
* The writer that will receive the output. * The writer that will receive the output.
*/ */
protected Writer writer; protected Appendable writer;
/** /**
* Make a fresh JSONWriter. It can be used to build one JSON text. * Make a fresh JSONWriter. It can be used to build one JSON text.
*/ */
public JSONWriter(Writer w) { public JSONWriter(Appendable w) {
this.comma = false; this.comma = false;
this.mode = 'i'; this.mode = 'i';
this.stack = new JSONObject[maxdepth]; this.stack = new JSONObject[maxdepth];
@@ -114,10 +116,13 @@ public class JSONWriter {
if (this.mode == 'o' || this.mode == 'a') { if (this.mode == 'o' || this.mode == 'a') {
try { try {
if (this.comma && this.mode == 'a') { if (this.comma && this.mode == 'a') {
this.writer.write(','); this.writer.append(',');
} }
this.writer.write(string); this.writer.append(string);
} catch (IOException e) { } catch (IOException e) {
// Android as of API 25 does not support this exception constructor
// however we won't worry about it. If an exception is happening here
// it will just throw a "Method not found" exception instead.
throw new JSONException(e); throw new JSONException(e);
} }
if (this.mode == 'o') { if (this.mode == 'o') {
@@ -150,21 +155,24 @@ public class JSONWriter {
/** /**
* End something. * End something.
* @param mode Mode * @param m Mode
* @param c Closing character * @param c Closing character
* @return this * @return this
* @throws JSONException If unbalanced. * @throws JSONException If unbalanced.
*/ */
private JSONWriter end(char mode, char c) throws JSONException { private JSONWriter end(char m, char c) throws JSONException {
if (this.mode != mode) { if (this.mode != m) {
throw new JSONException(mode == 'a' throw new JSONException(m == 'a'
? "Misplaced endArray." ? "Misplaced endArray."
: "Misplaced endObject."); : "Misplaced endObject.");
} }
this.pop(mode); this.pop(m);
try { try {
this.writer.write(c); this.writer.append(c);
} catch (IOException e) { } catch (IOException e) {
// Android as of API 25 does not support this exception constructor
// however we won't worry about it. If an exception is happening here
// it will just throw a "Method not found" exception instead.
throw new JSONException(e); throw new JSONException(e);
} }
this.comma = true; this.comma = true;
@@ -205,16 +213,24 @@ public class JSONWriter {
} }
if (this.mode == 'k') { if (this.mode == 'k') {
try { try {
this.stack[this.top - 1].putOnce(string, Boolean.TRUE); JSONObject topObject = this.stack[this.top - 1];
// don't use the built in putOnce method to maintain Android support
if(topObject.has(string)) {
throw new JSONException("Duplicate key \"" + string + "\"");
}
topObject.put(string, true);
if (this.comma) { if (this.comma) {
this.writer.write(','); this.writer.append(',');
} }
this.writer.write(JSONObject.quote(string)); this.writer.append(JSONObject.quote(string));
this.writer.write(':'); this.writer.append(':');
this.comma = false; this.comma = false;
this.mode = 'o'; this.mode = 'o';
return this; return this;
} catch (IOException e) { } catch (IOException e) {
// Android as of API 25 does not support this exception constructor
// however we won't worry about it. If an exception is happening here
// it will just throw a "Method not found" exception instead.
throw new JSONException(e); throw new JSONException(e);
} }
} }
@@ -281,6 +297,81 @@ public class JSONWriter {
this.top += 1; this.top += 1;
} }
/**
* Make a JSON text of an Object value. If the object has an
* value.toJSONString() method, then that method will be used to produce the
* JSON text. The method is required to produce a strictly conforming text.
* If the object does not contain a toJSONString method (which is the most
* common case), then a text will be produced by other means. If the value
* is an array or Collection, then a JSONArray will be made from it and its
* toJSONString method will be called. If the value is a MAP, then a
* JSONObject will be made from it and its toJSONString method will be
* called. Otherwise, the value's toString method will be called, and the
* result will be quoted.
*
* <p>
* Warning: This method assumes that the data structure is acyclical.
*
* @param value
* The value to be serialized.
* @return a printable, displayable, transmittable representation of the
* object, beginning with <code>{</code>&nbsp;<small>(left
* brace)</small> and ending with <code>}</code>&nbsp;<small>(right
* brace)</small>.
* @throws JSONException
* If the value is or contains an invalid number.
*/
public static String valueToString(Object value) throws JSONException {
if (value == null || value.equals(null)) {
return "null";
}
if (value instanceof JSONString) {
Object object;
try {
object = ((JSONString) value).toJSONString();
} catch (Exception e) {
throw new JSONException(e);
}
if (object instanceof String) {
return (String) object;
}
throw new JSONException("Bad value from toJSONString: " + object);
}
if (value instanceof Number) {
// not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
final String numberAsString = JSONObject.numberToString((Number) value);
try {
// Use the BigDecimal constructor for it's parser to validate the format.
@SuppressWarnings("unused")
BigDecimal unused = new BigDecimal(numberAsString);
// Close enough to a JSON number that we will return it unquoted
return numberAsString;
} catch (NumberFormatException ex){
// The Number value is not a valid JSON number.
// Instead we will quote it as a string
return JSONObject.quote(numberAsString);
}
}
if (value instanceof Boolean || value instanceof JSONObject
|| value instanceof JSONArray) {
return value.toString();
}
if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
return new JSONObject(map).toString();
}
if (value instanceof Collection) {
Collection<?> coll = (Collection<?>) value;
return new JSONArray(coll).toString();
}
if (value.getClass().isArray()) {
return new JSONArray(value).toString();
}
if(value instanceof Enum<?>){
return JSONObject.quote(((Enum<?>)value).name());
}
return JSONObject.quote(value.toString());
}
/** /**
* Append either the value <code>true</code> or the value * Append either the value <code>true</code> or the value
@@ -322,6 +413,6 @@ public class JSONWriter {
* @throws JSONException If the value is out of sequence. * @throws JSONException If the value is out of sequence.
*/ */
public JSONWriter value(Object object) throws JSONException { public JSONWriter value(Object object) throws JSONException {
return this.append(JSONObject.valueToString(object)); return this.append(valueToString(object));
} }
} }

View File

@@ -1,3 +1,5 @@
============================================================================
Copyright (c) 2002 JSON.org Copyright (c) 2002 JSON.org
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@@ -25,7 +25,6 @@ SOFTWARE.
*/ */
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties; import java.util.Properties;
/** /**
@@ -41,6 +40,8 @@ public class Property {
* @throws JSONException * @throws JSONException
*/ */
public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException {
// can't use the new constructor for Android support
// JSONObject jo = new JSONObject(properties == null ? 0 : properties.size());
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject();
if (properties != null && !properties.isEmpty()) { if (properties != null && !properties.isEmpty()) {
Enumeration<?> enumProperties = properties.propertyNames(); Enumeration<?> enumProperties = properties.propertyNames();
@@ -61,10 +62,12 @@ public class Property {
public static Properties toProperties(JSONObject jo) throws JSONException { public static Properties toProperties(JSONObject jo) throws JSONException {
Properties properties = new Properties(); Properties properties = new Properties();
if (jo != null) { if (jo != null) {
Iterator<String> keys = jo.keys(); // Don't use the new entrySet API to maintain Android support
while (keys.hasNext()) { for (final String key : jo.keySet()) {
String name = keys.next(); Object value = jo.opt(key);
properties.put(name, jo.getString(name)); if (!JSONObject.NULL.equals(value)) {
properties.put(key, value.toString());
}
} }
} }
return properties; return properties;

View File

@@ -1,4 +1,7 @@
JSON in Java [package org.json] JSON in Java [package org.json]
===============================
[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json)
JSON is a light-weight, language independent, data interchange format. JSON is a light-weight, language independent, data interchange format.
See http://www.JSON.org/ See http://www.JSON.org/
@@ -18,64 +21,63 @@ package.
The package compiles on Java 1.6-1.8. The package compiles on Java 1.6-1.8.
JSONObject.java: The JSONObject can parse text from a String or a JSONTokener **JSONObject.java**: The `JSONObject` can parse text from a `String` or a `JSONTokener`
to produce a map-like object. The object provides methods for manipulating its to produce a map-like object. The object provides methods for manipulating its
contents, and for producing a JSON compliant object serialization. contents, and for producing a JSON compliant object serialization.
JSONArray.java: The JSONObject can parse text from a String or a JSONTokener **JSONArray.java**: The `JSONArray` can parse text from a String or a `JSONTokener`
to produce a vector-like object. The object provides methods for manipulating to produce a vector-like object. The object provides methods for manipulating
its contents, and for producing a JSON compliant array serialization. its contents, and for producing a JSON compliant array serialization.
JSONTokener.java: The JSONTokener breaks a text into a sequence of individual **JSONTokener.java**: The `JSONTokener` breaks a text into a sequence of individual
tokens. It can be constructed from a String, Reader, or InputStream. tokens. It can be constructed from a `String`, `Reader`, or `InputStream`.
JSONException.java: The JSONException is the standard exception type thrown **JSONException.java**: The `JSONException` is the standard exception type thrown
by this package. by this package.
JSONPointer.java: Implementation of **JSONPointer.java**: Implementation of
[JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901). Supports [JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901). Supports
JSON Pointers both in the form of string representation and URI fragment JSON Pointers both in the form of string representation and URI fragment
representation. representation.
JSONString.java: The JSONString interface requires a toJSONString method, **JSONString.java**: The `JSONString` interface requires a `toJSONString` method,
allowing an object to provide its own serialization. allowing an object to provide its own serialization.
JSONStringer.java: The JSONStringer provides a convenient facility for **JSONStringer.java**: The `JSONStringer` provides a convenient facility for
building JSON strings. building JSON strings.
JSONWriter.java: The JSONWriter provides a convenient facility for building **JSONWriter.java**: The `JSONWriter` provides a convenient facility for building
JSON text through a writer. JSON text through a writer.
CDL.java: CDL provides support for converting between JSON and comma **CDL.java**: `CDL` provides support for converting between JSON and comma
delimited lists. delimited lists.
Cookie.java: Cookie provides support for converting between JSON and cookies. **Cookie.java**: `Cookie` provides support for converting between JSON and cookies.
CookieList.java: CookieList provides support for converting between JSON and **CookieList.java**: `CookieList` provides support for converting between JSON and
cookie lists. cookie lists.
HTTP.java: HTTP provides support for converting between JSON and HTTP headers. **HTTP.java**: `HTTP` provides support for converting between JSON and HTTP headers.
HTTPTokener.java: HTTPTokener extends JSONTokener for parsing HTTP headers. **HTTPTokener.java**: `HTTPTokener` extends `JSONTokener` for parsing HTTP headers.
XML.java: XML provides support for converting between JSON and XML. **XML.java**: `XML` provides support for converting between JSON and XML.
JSONML.java: JSONML provides support for converting between JSONML and XML. **JSONML.java**: `JSONML` provides support for converting between JSONML and XML.
XMLTokener.java: XMLTokener extends JSONTokener for parsing XML text. **XMLTokener.java**: `XMLTokener` extends `JSONTokener` for parsing XML text.
Unit tests are maintained in a separate project. Contributing developers can test Unit tests are maintained in a separate project. Contributing developers can test
JSON-java pull requests with the code in this project: JSON-java pull requests with the code in this project:
https://github.com/stleary/JSON-Java-unit-test https://github.com/stleary/JSON-Java-unit-test
Numeric types in this package comply with ECMA-404: The JSON Data Interchange Format Numeric types in this package comply with
(http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) and [ECMA-404: The JSON Data Interchange Format](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) and
RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159#section-6).
(https://tools.ietf.org/html/rfc7159#section-6). This package fully supports `Integer`, `Long`, and `Double` Java types. Partial support
This package fully supports Integer, Long, and Double Java types. Partial support for `BigInteger` and `BigDecimal` values in `JSONObject` and `JSONArray` objects is provided
for BigInteger and BigDecimal values in JSONObject and JSONArray objects is provided in the form of `get()`, `opt()`, and `put()` API methods.
in the form of get(), opt(), and put() API methods.
Although 1.6 compatibility is currently supported, it is not a project goal and may be Although 1.6 compatibility is currently supported, it is not a project goal and may be
removed in some future release. removed in some future release.
@@ -90,12 +92,19 @@ invalid number formats (1.2e6.3) will cause errors as such documents can not be
Release history: Release history:
~~~
20180130 Recent commits
20171018 Checkpoint for recent commits.
20170516 Roll up recent commits.
20160810 Revert code that was breaking opt*() methods. 20160810 Revert code that was breaking opt*() methods.
20160807 This release contains a bug in the JSONObject.opt*() and JSONArray.opt*() methods, 20160807 This release contains a bug in the JSONObject.opt*() and JSONArray.opt*() methods,
it is not recommended for use. it is not recommended for use.
Java 1.6 compatability fixed, JSONArray.toList() and JSONObject.toMap(), Java 1.6 compatability fixed, JSONArray.toList() and JSONObject.toMap(),
RFC4180 compatibility, JSONPointer, some exception fixes, optional XML type conversion. RFC4180 compatibility, JSONPointer, some exception fixes, optional XML type conversion.
Contains the latest code as of 7 Aug, 2016 Contains the latest code as of 7 Aug, 2016
20160212 Java 1.6 compatibility, OSGi bundle. Contains the latest code as of 12 Feb, 2016. 20160212 Java 1.6 compatibility, OSGi bundle. Contains the latest code as of 12 Feb, 2016.
@@ -103,9 +112,11 @@ Contains the latest code as of 7 Aug, 2016
20151123 JSONObject and JSONArray initialization with generics. Contains the 20151123 JSONObject and JSONArray initialization with generics. Contains the
latest code as of 23 Nov, 2015. latest code as of 23 Nov, 2015.
20150729 Checkpoint for Maven central repository release. Contains the latest code 20150729 Checkpoint for Maven central repository release. Contains the latest code
as of 29 July, 2015. as of 29 July, 2015.
~~~
JSON-java releases can be found by searching the Maven repository for groupId "org.json"
and artifactId "json". For example: JSON-java releases can be found by searching the Maven repository for groupId "org.json"
and artifactId "json". For example:
https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.json%22%20AND%20a%3A%22json%22 https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.json%22%20AND%20a%3A%22json%22

226
XML.java
View File

@@ -31,11 +31,10 @@ import java.util.Iterator;
* covert a JSONObject into an XML text. * covert a JSONObject into an XML text.
* *
* @author JSON.org * @author JSON.org
* @version 2016-01-30 * @version 2016-08-10
*/ */
@SuppressWarnings("boxing") @SuppressWarnings("boxing")
public class XML { public class XML {
/** The Character '&amp;'. */ /** The Character '&amp;'. */
public static final Character AMP = '&'; public static final Character AMP = '&';
@@ -62,6 +61,46 @@ public class XML {
/** The Character '/'. */ /** The Character '/'. */
public static final Character SLASH = '/'; public static final Character SLASH = '/';
/**
* Creates an iterator for navigating Code Points in a string instead of
* characters. Once Java7 support is dropped, this can be replaced with
* <code>
* string.codePoints()
* </code>
* which is available in Java8 and above.
*
* @see <a href=
* "http://stackoverflow.com/a/21791059/6030888">http://stackoverflow.com/a/21791059/6030888</a>
*/
private static Iterable<Integer> codePointIterator(final String string) {
return new Iterable<Integer>() {
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
private int nextIndex = 0;
private int length = string.length();
@Override
public boolean hasNext() {
return this.nextIndex < this.length;
}
@Override
public Integer next() {
int result = string.codePointAt(this.nextIndex);
this.nextIndex += Character.charCount(result);
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/** /**
* Replace special characters with XML escapes: * Replace special characters with XML escapes:
@@ -71,6 +110,7 @@ public class XML {
* &lt; <small>(less than)</small> is replaced by &amp;lt; * &lt; <small>(less than)</small> is replaced by &amp;lt;
* &gt; <small>(greater than)</small> is replaced by &amp;gt; * &gt; <small>(greater than)</small> is replaced by &amp;gt;
* &quot; <small>(double quote)</small> is replaced by &amp;quot; * &quot; <small>(double quote)</small> is replaced by &amp;quot;
* &apos; <small>(single quote / apostrophe)</small> is replaced by &amp;apos;
* </pre> * </pre>
* *
* @param string * @param string
@@ -79,9 +119,8 @@ public class XML {
*/ */
public static String escape(String string) { public static String escape(String string) {
StringBuilder sb = new StringBuilder(string.length()); StringBuilder sb = new StringBuilder(string.length());
for (int i = 0, length = string.length(); i < length; i++) { for (final int cp : codePointIterator(string)) {
char c = string.charAt(i); switch (cp) {
switch (c) {
case '&': case '&':
sb.append("&amp;"); sb.append("&amp;");
break; break;
@@ -98,6 +137,69 @@ public class XML {
sb.append("&apos;"); sb.append("&apos;");
break; break;
default: default:
if (mustEscape(cp)) {
sb.append("&#x");
sb.append(Integer.toHexString(cp));
sb.append(';');
} else {
sb.appendCodePoint(cp);
}
}
}
return sb.toString();
}
/**
* @param cp code point to test
* @return true if the code point is not valid for an XML
*/
private static boolean mustEscape(int cp) {
/* Valid range from https://www.w3.org/TR/REC-xml/#charsets
*
* #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
*
* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
*/
// isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F)
// all ISO control characters are out of range except tabs and new lines
return (Character.isISOControl(cp)
&& cp != 0x9
&& cp != 0xA
&& cp != 0xD
) || !(
// valid the range of acceptable characters that aren't control
(cp >= 0x20 && cp <= 0xD7FF)
|| (cp >= 0xE000 && cp <= 0xFFFD)
|| (cp >= 0x10000 && cp <= 0x10FFFF)
)
;
}
/**
* Removes XML escapes from the string.
*
* @param string
* string to remove escapes from
* @return string with converted entities
*/
public static String unescape(String string) {
StringBuilder sb = new StringBuilder(string.length());
for (int i = 0, length = string.length(); i < length; i++) {
char c = string.charAt(i);
if (c == '&') {
final int semic = string.indexOf(';', i);
if (semic > i) {
final String entity = string.substring(i + 1, semic);
sb.append(XMLTokener.unescapeEntity(entity));
// skip past the entity we just parsed.
i += entity.length() + 1;
} else {
// this shouldn't happen in most cases since the parser
// errors on unclosed entries.
sb.append(c);
}
} else {
// not part of an entity
sb.append(c); sb.append(c);
} }
} }
@@ -227,7 +329,6 @@ public class XML {
if (token == null) { if (token == null) {
token = x.nextToken(); token = x.nextToken();
} }
// attribute = value // attribute = value
if (token instanceof String) { if (token instanceof String) {
string = (String) token; string = (String) token;
@@ -238,7 +339,7 @@ public class XML {
throw x.syntaxError("Missing value"); throw x.syntaxError("Missing value");
} }
jsonobject.accumulate(string, jsonobject.accumulate(string,
keepStrings ? token : JSONObject.stringToValue((String) token)); keepStrings ? ((String)token) : stringToValue((String) token));
token = null; token = null;
} else { } else {
jsonobject.accumulate(string, ""); jsonobject.accumulate(string, "");
@@ -270,7 +371,7 @@ public class XML {
string = (String) token; string = (String) token;
if (string.length() > 0) { if (string.length() > 0) {
jsonobject.accumulate("content", jsonobject.accumulate("content",
keepStrings ? token : JSONObject.stringToValue(string)); keepStrings ? string : stringToValue(string));
} }
} else if (token == LT) { } else if (token == LT) {
@@ -297,16 +398,56 @@ public class XML {
} }
/** /**
* This method has been deprecated in favor of the * This method is the same as {@link JSONObject#stringToValue(String)}.
* {@link JSONObject.stringToValue(String)} method. Use it instead.
* *
* @deprecated Use {@link JSONObject#stringToValue(String)} instead.
* @param string String to convert * @param string String to convert
* @return JSON value of this string or the string * @return JSON value of this string or the string
*/ */
@Deprecated // To maintain compatibility with the Android API, this method is a direct copy of
// the one in JSONObject. Changes made here should be reflected there.
public static Object stringToValue(String string) { public static Object stringToValue(String string) {
return JSONObject.stringToValue(string); if (string.equals("")) {
return string;
}
if (string.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}
if (string.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
if (string.equalsIgnoreCase("null")) {
return JSONObject.NULL;
}
/*
* If it might be a number, try converting it. If a number cannot be
* produced, then the value will just be a string.
*/
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
// if we want full Big Number support this block can be replaced with:
// return stringToNumber(string);
if (string.indexOf('.') > -1 || string.indexOf('e') > -1
|| string.indexOf('E') > -1 || "-0".equals(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;
}
}
} catch (Exception ignore) {
}
}
return string;
} }
/** /**
@@ -354,8 +495,11 @@ public class XML {
public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject();
XMLTokener x = new XMLTokener(string); XMLTokener x = new XMLTokener(string);
while (x.more() && x.skipPast("<")) { while (x.more()) {
parse(x, jo, null, keepStrings); x.skipPast("<");
if(x.more()) {
parse(x, jo, null, keepStrings);
}
} }
return jo; return jo;
} }
@@ -381,15 +525,12 @@ public class XML {
* @return A string. * @return A string.
* @throws JSONException Thrown if there is an error parsing the string * @throws JSONException Thrown if there is an error parsing the string
*/ */
public static String toString(Object object, String tagName) public static String toString(final Object object, final String tagName)
throws JSONException { throws JSONException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
JSONArray ja; JSONArray ja;
JSONObject jo; JSONObject jo;
String key;
Iterator<String> keys;
String string; String string;
Object value;
if (object instanceof JSONObject) { if (object instanceof JSONObject) {
@@ -401,29 +542,28 @@ public class XML {
} }
// Loop thru the keys. // Loop thru the keys.
// don't use the new entrySet accessor to maintain Android Support
jo = (JSONObject) object; jo = (JSONObject) object;
keys = jo.keys(); for (final String key : jo.keySet()) {
while (keys.hasNext()) { Object value = jo.opt(key);
key = keys.next();
value = jo.opt(key);
if (value == null) { if (value == null) {
value = ""; value = "";
} else if (value.getClass().isArray()) { } else if (value.getClass().isArray()) {
value = new JSONArray(value); value = new JSONArray(value);
} }
string = value instanceof String ? (String) value : null;
// Emit content in body // Emit content in body
if ("content".equals(key)) { if ("content".equals(key)) {
if (value instanceof JSONArray) { if (value instanceof JSONArray) {
ja = (JSONArray) value; ja = (JSONArray) value;
int i = 0; int jaLength = ja.length();
for (Object val : ja) { // don't use the new iterator API to maintain support for Android
for (int i = 0; i < jaLength; i++) {
if (i > 0) { if (i > 0) {
sb.append('\n'); sb.append('\n');
} }
Object val = ja.opt(i);
sb.append(escape(val.toString())); sb.append(escape(val.toString()));
i++;
} }
} else { } else {
sb.append(escape(value.toString())); sb.append(escape(value.toString()));
@@ -433,7 +573,10 @@ public class XML {
} else if (value instanceof JSONArray) { } else if (value instanceof JSONArray) {
ja = (JSONArray) value; ja = (JSONArray) value;
for (Object val : ja) { int jaLength = ja.length();
// don't use the new iterator API to maintain support for Android
for (int i = 0; i < jaLength; i++) {
Object val = ja.opt(i);
if (val instanceof JSONArray) { if (val instanceof JSONArray) {
sb.append('<'); sb.append('<');
sb.append(key); sb.append(key);
@@ -468,21 +611,22 @@ public class XML {
} }
if (object != null) { if (object != null && (object instanceof JSONArray || object.getClass().isArray())) {
if (object.getClass().isArray()) { if(object.getClass().isArray()) {
object = new JSONArray(object); ja = new JSONArray(object);
} } else {
if (object instanceof JSONArray) {
ja = (JSONArray) object; ja = (JSONArray) object;
for (Object val : ja) {
// XML does not have good support for arrays. If an array
// appears in a place where XML is lacking, synthesize an
// <array> element.
sb.append(toString(val, tagName == null ? "array" : tagName));
}
return sb.toString();
} }
int jaLength = ja.length();
// don't use the new iterator API to maintain support for Android
for (int i = 0; i < jaLength; i++) {
Object val = ja.opt(i);
// XML does not have good support for arrays. If an array
// appears in a place where XML is lacking, synthesize an
// <array> element.
sb.append(toString(val, tagName == null ? "array" : tagName));
}
return sb.toString();
} }
string = (object == null) ? "null" : escape(object.toString()); string = (object == null) ? "null" : escape(object.toString());

View File

@@ -64,11 +64,8 @@ public class XMLTokener extends JSONTokener {
char c; char c;
int i; int i;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (;;) { while (more()) {
c = next(); c = next();
if (end()) {
throw syntaxError("Unclosed CDATA");
}
sb.append(c); sb.append(c);
i = sb.length() - 3; i = sb.length() - 3;
if (i >= 0 && sb.charAt(i) == ']' && if (i >= 0 && sb.charAt(i) == ']' &&
@@ -77,6 +74,7 @@ public class XMLTokener extends JSONTokener {
return sb.toString(); return sb.toString();
} }
} }
throw syntaxError("Unclosed CDATA");
} }
@@ -103,7 +101,10 @@ public class XMLTokener extends JSONTokener {
} }
sb = new StringBuilder(); sb = new StringBuilder();
for (;;) { for (;;) {
if (c == '<' || c == 0) { if (c == 0) {
return sb.toString().trim();
}
if (c == '<') {
back(); back();
return sb.toString().trim(); return sb.toString().trim();
} }
@@ -137,8 +138,37 @@ public class XMLTokener extends JSONTokener {
} }
} }
String string = sb.toString(); String string = sb.toString();
Object object = entity.get(string); return unescapeEntity(string);
return object != null ? object : ampersand + string + ";"; }
/**
* Unescapes an XML entity encoding;
* @param e entity (only the actual entity value, not the preceding & or ending ;
* @return
*/
static String unescapeEntity(String e) {
// validate
if (e == null || e.isEmpty()) {
return "";
}
// if our entity is an encoded unicode point, parse it.
if (e.charAt(0) == '#') {
int cp;
if (e.charAt(1) == 'x') {
// hex encoded unicode
cp = Integer.parseInt(e.substring(2), 16);
} else {
// decimal encoded unicode
cp = Integer.parseInt(e.substring(1));
}
return new String(new int[] {cp},0,1);
}
Character knownEntity = entity.get(e);
if(knownEntity==null) {
// we don't know the entity so keep it encoded
return '&' + e + ';';
}
return knownEntity.toString();
} }
@@ -296,9 +326,11 @@ public class XMLTokener extends JSONTokener {
* Skip characters until past the requested string. * Skip characters until past the requested string.
* If it is not found, we are left at the end of the source with a result of false. * If it is not found, we are left at the end of the source with a result of false.
* @param to A string to skip past. * @param to A string to skip past.
* @throws JSONException
*/ */
public boolean skipPast(String to) throws JSONException { // The Android implementation of JSONTokener has a public method of public void skipPast(String to)
// even though ours does not have that method, to have API compatibility, our method in the subclass
// should match.
public void skipPast(String to) {
boolean b; boolean b;
char c; char c;
int i; int i;
@@ -315,7 +347,7 @@ public class XMLTokener extends JSONTokener {
for (i = 0; i < length; i += 1) { for (i = 0; i < length; i += 1) {
c = next(); c = next();
if (c == 0) { if (c == 0) {
return false; return;
} }
circle[i] = c; circle[i] = c;
} }
@@ -342,14 +374,14 @@ public class XMLTokener extends JSONTokener {
/* If we exit the loop with b intact, then victory is ours. */ /* If we exit the loop with b intact, then victory is ours. */
if (b) { if (b) {
return true; return;
} }
/* Get the next character. If there isn't one, then defeat is ours. */ /* Get the next character. If there isn't one, then defeat is ours. */
c = next(); c = next();
if (c == 0) { if (c == 0) {
return false; return;
} }
/* /*
* Shove the character in the circle buffer and advance the * Shove the character in the circle buffer and advance the