diff --git a/.gitignore b/.gitignore index 9e7b59c..b7025e6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ build .classpath .project .settings/ +/.gradle/ +/gradle/ +/gradlew +/gradlew.bat +.gitmodules +src/main/ +.idea diff --git a/README.md b/README.md index d08751d..63d0fd7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # JSON-Java-unit-test + +# This project is no longer accepting pull requests. It is not the source of truth for JSON-Java unit tests. The unit tests have been ported to [https://github.com/stleary/JSON-java](https://github.com/stleary/JSON-java) so that code and tests reside together. Please submit all pull requests to the new location. + Unit tests to validate the JSON-Java GitHub project code
-https://github.com/douglascrockford/JSON-java
+https://github.com/stleary/JSON-java
Gradle and Eclipse is the recommended build tool and IDE.
Run individual tests or JunitTestSuite using EclEmma Coverage, or execute the **TestRunner** application directly.
@@ -28,17 +31,32 @@ git clone https://github.com/stleary/JSON-Java-unit-test.git . ```` \# Create a directory structure for the JSON-Java code ```` -# Windows version -md /s src\org\json +# Windows 10 version +mkdir src\main\java\org\json +# *nix version +mkdir -p src/main/java/org/json ```` \# clone JSON-Java ```` -git clone https://github.com/stleary/JSON-Java.git src\org\json +#Windows version +git clone https://github.com/stleary/JSON-Java.git src\main\java\org\json + +#*Nix version +git clone https://github.com/stleary/JSON-Java.git src/main/java/org/json ```` \# Build, then execute the unit tests and code coverage ```` gradle clean build test jacocoTestReport + +```` +\# Eclipse setup requires the Gradle IDE plug-in
+\# I use Gradle IDE 3.6.4.201503050952-RELEASE org.springsource.ide.eclipse.gradle.feature.feature.group Pivotal Software, Inc.
+\# From the Eclipse IDE: ```` +File > Import > Gradle project > (navigate to your directory) > Build Model > (Select your directory) > Finish +(It is not necessary to run "gradle eclipse" on the project, from the command line) +```` + Unit test results will be in build\reports\tests\index.html
Code coverage will be in build\reports\jacoco\html\index.html @@ -55,7 +73,7 @@ When adding a new unit test, don't forget to update JunitTestSuite.java. The fundamental issues with JSON-Java testing are:
* JSONObjects are unordered, making simple string comparison ineffective. -* Comparisons via **equals()** is not currently supported. Neither JSONArray nor JSONObject overrride hashCode() or equals(), so comparison defaults to the Object equals(), which is not useful. +* Comparisons via **equals()** is not currently supported. Neither JSONArray nor JSONObject override hashCode() or equals(), so comparison defaults to the Object equals(), which is not useful. * Access to the JSONArray and JSONObject internal containers for comparison is not currently available. General issues with unit testing are:
@@ -67,43 +85,17 @@ When adding a new unit test, don't forget to update JunitTestSuite.java. When you start working on a test, add the empty file to the repository and update the readme, so that others will know that test is taken. -A unit test has the following stages: - -| Test phase |Description | -|----|----| -| No test | No test specifically for this class has been written, or the class contains no executable code. | -| In progress | Unit tests have been started for this class. | -| Coverage > 90% | Initial goal of 90% coverage has been reached. Test quality may be questionable | -| Reasonable test cases | 90% coverage. Functionality and behavior has been confirmed | -| Checked against previous unit tests | Historical unit tests have been checked in case something important was missed | -| Completed | The unit test is completed | - - -| Test file name | Coverage | Comments | -| ------------- | ------------- | ---- | -| Total coverage | 90.6% | | | -| | | | -| CDL.java | 98.8% | Reasonable test cases. | -| Cookie.java | 98.9% | Reasonable test cases. | -| CookieList.java |96.5% | Reasonable test cases. | -| EnumTest.java | n/a | Just documenting how enums are handled. | -| HTTP.java | 98.7%| Coverage > 90% | -| HTTPTokener.java |93.2% | No test | -| JSONArray.java |95.9% | Reasonable test cases | -| JSONException.java | 26.7% | No test | -| JSONML.java | 86.8%| In progress | -| JSONObject | 94.0% | Reasonable test cases | -| JSONObject.Null | 87.5% | No test | -| JSONString.java | | No test | -| JSONStringer.java | 93.8%| Coverage > 90% | -| JSONTokener.java | 72.1% | In progress | -| JSONWriter.java | 87.5% | No test | -| Property.java | 94.8% | Coverage > 90% | -| XML.java | 87.4% | In progress | -| XMLTokener.java| 82.7%| No test | - -| Files used in test | +**Caveats:** +JSON-Java is Java 1.6-compatible, but JSON-Java-unit-tests requires Java 1.8. If you see this error when building JSON-Java-unit-test, make sure you have 1.8 installed, on your path, and set in JAVA_HOME: +``` +Execution failed for task ':compileJava'. +> invalid flag: -parameters +``` + + +| Resource files used in test | | ------------- | +| EnumTest.java | | MyBean.java | | MyBigNumberBean.java | | MyEnum.java | diff --git a/build.gradle b/build.gradle index ad186a1..43656ae 100644 --- a/build.gradle +++ b/build.gradle @@ -2,23 +2,16 @@ apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'jacoco' +tasks.withType(JavaCompile) { + // this subproject requires -parameters option + options.compilerArgs << '-parameters' + options.encoding = 'UTF-8' +} + sourceSets { // Uncomment main if you have merged JSON-Java and JSON-Java-unit-test code - main { - java { - srcDir 'src' - exclude 'test/' - } - } - test { - java { - srcDir 'src/test' - exclude 'resources/' - } - resources { - srcDir 'resources' - } - } + main + test } repositories { @@ -35,7 +28,10 @@ dependencies { // testCompile files('./JSON-Java.jar') } -test { finalizedBy jacocoTestReport } +test { + include "org/json/junit/JunitTestSuite.class" + finalizedBy jacocoTestReport +} jacocoTestReport{ additionalSourceDirs = files(sourceSets.main.allJava.srcDirs) reports { diff --git a/src/test/org/json/junit/CDLTest.java b/src/test/java/org/json/junit/CDLTest.java similarity index 77% rename from src/test/org/json/junit/CDLTest.java rename to src/test/java/org/json/junit/CDLTest.java index a40b014..721fd3c 100644 --- a/src/test/org/json/junit/CDLTest.java +++ b/src/test/java/org/json/junit/CDLTest.java @@ -30,7 +30,7 @@ public class CDLTest { ); /** - * CDL.toJSONArray() adds all values asstrings, with no filtering or + * CDL.toJSONArray() adds all values as strings, with no filtering or * conversions. For testing, this means that the expected JSONObject * values all must be quoted in the cases where the JSONObject parsing * might normally convert the value into a non-string. @@ -61,11 +61,11 @@ public void unbalancedQuoteInName() { String badLine = "Col1, \"Col2\nVal1, Val2"; try { CDL.toJSONArray(badLine); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Missing close quote '\"'. at 12 [character 0 line 2]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Missing close quote '\"'. at 12 [character 0 line 2]", + e.getMessage()); } } @@ -78,11 +78,11 @@ public void unbalancedQuoteInValue() { String badLine = "Col1, Col2\n\"Val1, Val2"; try { CDL.toJSONArray(badLine); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Missing close quote '\"'. at 23 [character 12 line 3]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Missing close quote '\"'. at 22 [character 11 line 2]", + e.getMessage()); } } @@ -96,11 +96,11 @@ public void nullInName() { String badLine = "C\0ol1, Col2\nVal1, Val2"; try { CDL.toJSONArray(badLine); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Bad character 'o' (111). at 3 [character 4 line 1]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Bad character 'o' (111). at 2 [character 3 line 1]", + e.getMessage()); } } @@ -114,11 +114,11 @@ public void unbalancedEscapedQuote(){ String badLine = "Col1, Col2\n\"Val1, \"\"Val2\"\""; try { CDL.toJSONArray(badLine); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Missing close quote '\"'. at 27 [character 16 line 3]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Missing close quote '\"'. at 26 [character 15 line 2]", + e.getMessage()); } } @@ -128,7 +128,7 @@ public void unbalancedEscapedQuote(){ */ @Test public void singleEscapedQuote(){ - String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\""; + String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\""; JSONArray jsonArray = CDL.toJSONArray(singleEscape); String cdlStr = CDL.toString(jsonArray); @@ -136,7 +136,22 @@ public void singleEscapedQuote(){ assertTrue(cdlStr.contains("Col2")); assertTrue(cdlStr.contains("Val1")); assertTrue(cdlStr.contains("\"Val2")); + } + + /** + * Assert that there is no error for a single escaped quote within a properly + * embedded quote when not the last value. + */ + @Test + public void singleEscapedQuoteMiddleString(){ + String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\"\nVal 3,Val 4"; + JSONArray jsonArray = CDL.toJSONArray(singleEscape); + String cdlStr = CDL.toString(jsonArray); + assertTrue(cdlStr.contains("Col1")); + assertTrue(cdlStr.contains("Col2")); + assertTrue(cdlStr.contains("Val1")); + assertTrue(cdlStr.contains("\"Val2")); } /** @@ -149,12 +164,12 @@ public void badEscapedQuote(){ try { CDL.toJSONArray(badLine); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { System.out.println("Message" + e.getMessage()); - assertTrue("Expecting an exception message", - "Bad character 'V' (86). at 20 [character 9 line 3]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Bad character 'V' (86). at 20 [character 9 line 2]", + e.getMessage()); } @@ -186,8 +201,8 @@ public void emptyString() { public void onlyColumnNames() { String columnNameStr = "col1, col2, col3"; JSONArray jsonArray = CDL.toJSONArray(columnNameStr); - assertTrue("CDL should return null when only 1 row is given", - jsonArray == null); + assertNull("CDL should return null when only 1 row is given", + jsonArray); } /** @@ -197,8 +212,8 @@ public void onlyColumnNames() { public void emptyLinesToJSONArray() { String str = " , , , \n , , , "; JSONArray jsonArray = CDL.toJSONArray(str); - assertTrue("JSONArray should be null for no content", - jsonArray == null); + assertNull("JSONArray should be null for no content", + jsonArray); } /** @@ -208,8 +223,8 @@ public void emptyLinesToJSONArray() { public void emptyJSONArrayToString() { JSONArray jsonArray = new JSONArray(); String str = CDL.toString(jsonArray); - assertTrue("CDL should return null for toString(null)", - str == null); + assertNull("CDL should return null for toString(null)", + str); } /** @@ -218,8 +233,8 @@ public void emptyJSONArrayToString() { @Test public void nullJSONArraysToString() { String str = CDL.toString(null, null); - assertTrue("CDL should return null for toString(null)", - str == null); + assertNull("CDL should return null for toString(null)", + str); } /** @@ -249,8 +264,8 @@ public void checkSpecialChars() { */ @Test public void textToJSONArray() { - JSONArray jsonArray = CDL.toJSONArray(lines); - JSONArray expectedJsonArray = new JSONArray(expectedLines); + JSONArray jsonArray = CDL.toJSONArray(this.lines); + JSONArray expectedJsonArray = new JSONArray(this.expectedLines); Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); } @@ -274,10 +289,10 @@ public void jsonArrayToJSONArray() { */ @Test public void textToJSONArrayAndBackToString() { - JSONArray jsonArray = CDL.toJSONArray(lines); + JSONArray jsonArray = CDL.toJSONArray(this.lines); String jsonStr = CDL.toString(jsonArray); JSONArray finalJsonArray = CDL.toJSONArray(jsonStr); - JSONArray expectedJsonArray = new JSONArray(expectedLines); + JSONArray expectedJsonArray = new JSONArray(this.expectedLines); Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray); } diff --git a/src/test/org/json/junit/CookieListTest.java b/src/test/java/org/json/junit/CookieListTest.java similarity index 95% rename from src/test/org/json/junit/CookieListTest.java rename to src/test/java/org/json/junit/CookieListTest.java index 7a710db..7149644 100644 --- a/src/test/org/json/junit/CookieListTest.java +++ b/src/test/java/org/json/junit/CookieListTest.java @@ -47,14 +47,14 @@ public void malFormedCookieListException() { String cookieStr = "thisCookieHasNoEqualsChar"; try { CookieList.toJSONObject(cookieStr); - assertTrue("should throw an exception", false); + fail("should throw an exception"); } catch (JSONException e) { /** * Not sure of the missing char, but full string compare fails */ - assertTrue("Expecting an exception message", - e.getMessage().startsWith("Expected '=' and instead saw '") && - e.getMessage().endsWith("' at 27 [character 28 line 1]")); + assertEquals("Expecting an exception message", + "Expected '=' and instead saw '' at 25 [character 26 line 1]", + e.getMessage()); } } @@ -65,7 +65,7 @@ public void malFormedCookieListException() { public void emptyStringCookieList() { String cookieStr = ""; JSONObject jsonObject = CookieList.toJSONObject(cookieStr); - assertTrue(jsonObject.length() == 0); + assertTrue(jsonObject.isEmpty()); } /** diff --git a/src/test/org/json/junit/CookieTest.java b/src/test/java/org/json/junit/CookieTest.java similarity index 90% rename from src/test/org/json/junit/CookieTest.java rename to src/test/java/org/json/junit/CookieTest.java index 9104b60..4b7ca44 100644 --- a/src/test/org/json/junit/CookieTest.java +++ b/src/test/java/org/json/junit/CookieTest.java @@ -43,11 +43,11 @@ public void malFormedNameValueException() { String cookieStr = "thisCookieHasNoEqualsChar"; try { Cookie.toJSONObject(cookieStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - e.getMessage().startsWith("Expected '=' and instead saw '") - && e.getMessage().endsWith("' at 27 [character 28 line 1]")); + assertEquals("Expecting an exception message", + "Expected '=' and instead saw '' at 25 [character 26 line 1]", + e.getMessage()); } } @@ -61,11 +61,11 @@ public void malFormedAttributeException() { String cookieStr = "this=Cookie;myAttribute"; try { Cookie.toJSONObject(cookieStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Missing '=' in cookie parameter. at 25 [character 26 line 1]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Missing '=' in cookie parameter. at 23 [character 24 line 1]", + e.getMessage()); } } @@ -79,11 +79,11 @@ public void emptyStringCookieException() { String cookieStr = ""; try { Cookie.toJSONObject(cookieStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - e.getMessage().startsWith("Expected '=' and instead saw '") && - e.getMessage().endsWith("' at 2 [character 3 line 1]")); + assertEquals("Expecting an exception message", + "Expected '=' and instead saw '' at 0 [character 1 line 1]", + e.getMessage()); } } diff --git a/src/test/org/json/junit/EnumTest.java b/src/test/java/org/json/junit/EnumTest.java similarity index 78% rename from src/test/org/json/junit/EnumTest.java rename to src/test/java/org/json/junit/EnumTest.java index ff4b294..366643e 100644 --- a/src/test/org/json/junit/EnumTest.java +++ b/src/test/java/org/json/junit/EnumTest.java @@ -1,13 +1,22 @@ package org.json.junit; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; -import java.util.*; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; -import org.json.*; -import org.junit.*; +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.junit.data.MyEnum; +import org.json.junit.data.MyEnumClass; +import org.json.junit.data.MyEnumField; +import org.junit.Test; -import com.jayway.jsonpath.*; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.JsonPath; /** * Enums are not explicitly supported in JSON-Java. But because enums act like @@ -26,7 +35,7 @@ public void jsonObjectFromEnum() { // If there are no getters then the object is empty. MyEnum myEnum = MyEnum.VAL2; JSONObject jsonObject = new JSONObject(myEnum); - assertTrue("simple enum has no getters", jsonObject.length() == 0); + assertTrue("simple enum has no getters", jsonObject.isEmpty()); // enum with a getters should create a non-empty object MyEnumField myEnumField = MyEnumField.VAL2; @@ -50,11 +59,12 @@ public void jsonObjectFromEnum() { // validate JSON content doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expecting 2 items in top level object", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expecting 2 items in myEnumField object", ((Map)(JsonPath.read(doc, "$.myEnumField"))).size() == 2); - assertTrue("expecting 0 items in myEnum object", ((Map)(JsonPath.read(doc, "$.myEnum"))).size() == 0); - assertTrue("expecting 3", Integer.valueOf(3).equals(jsonObject.query("/myEnumField/intVal"))); - assertTrue("expecting val 3", "val 3".equals(jsonObject.query("/myEnumField/value"))); + assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); + assertTrue("expected 2 myEnumField items", "VAL3".equals((JsonPath.read(doc, "$.myEnumField")))); + assertTrue("expected 0 myEnum items", "VAL1".equals((JsonPath.read(doc, "$.myEnum")))); + + assertTrue("expecting MyEnumField.VAL3", MyEnumField.VAL3.equals(jsonObject.query("/myEnumField"))); + assertTrue("expecting MyEnum.VAL1", MyEnum.VAL1.equals(jsonObject.query("/myEnum"))); } /** @@ -86,7 +96,46 @@ public void jsonObjectFromEnumWithNames() { assertTrue("expected 3 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 3); assertTrue("expected VAL1", MyEnumField.VAL1.equals(jsonObject.query("/VAL1"))); assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonObject.query("/VAL2"))); - assertTrue("expected VAL3", myEnumField.VAL3.equals(jsonObject.query("/VAL3"))); + assertTrue("expected VAL3", MyEnumField.VAL3.equals(jsonObject.query("/VAL3"))); + } + + /** + * Verify that enums are handled consistently between JSONArray and JSONObject + */ + @Test + public void verifyEnumConsistency(){ + JSONObject jo = new JSONObject(); + + jo.put("value", MyEnumField.VAL2); + String expected="{\"value\":\"VAL2\"}"; + String actual = jo.toString(); + assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual)); + + jo.accumulate("value", MyEnumField.VAL1); + expected="{\"value\":[\"VAL2\",\"VAL1\"]}"; + actual = jo.toString(); + assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual)); + + jo.remove("value"); + jo.append("value", MyEnumField.VAL1); + expected="{\"value\":[\"VAL1\"]}"; + actual = jo.toString(); + assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual)); + + jo.put("value", EnumSet.of(MyEnumField.VAL2)); + expected="{\"value\":[\"VAL2\"]}"; + actual = jo.toString(); + assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual)); + + JSONArray ja = new JSONArray(); + ja.put(MyEnumField.VAL2); + jo.put("value", ja); + actual = jo.toString(); + assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual)); + + jo.put("value", new MyEnumField[]{MyEnumField.VAL2}); + actual = jo.toString(); + assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual)); } @@ -149,7 +198,7 @@ public void enumValueToString() { * However, an enum within another class will not be rendered * unless that class overrides default toString() */ - String expectedStr3 = "\"org.json.junit.MyEnumClass@"; + String expectedStr3 = "\"org.json.junit.data.MyEnumClass@"; myEnumClass.setMyEnum(MyEnum.VAL1); myEnumClass.setMyEnumField(MyEnumField.VAL1); String str3 = JSONObject.valueToString(myEnumClass); @@ -185,10 +234,8 @@ public void enumToString() { // validate JSON content doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expected 2 myEnumField items", ((Map)(JsonPath.read(doc, "$.myEnumField"))).size() == 2); - assertTrue("expected 0 myEnum items", ((Map)(JsonPath.read(doc, "$.myEnum"))).size() == 0); - assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/myEnumField/intVal"))); - assertTrue("expected val 3", "val 3".equals(jsonObject.query("/myEnumField/value"))); + assertTrue("expected VAL3", "VAL3".equals((JsonPath.read(doc, "$.myEnumField")))); + assertTrue("expected VAL1", "VAL1".equals((JsonPath.read(doc, "$.myEnum")))); String [] names = JSONObject.getNames(myEnum); jsonObject = new JSONObject(myEnum, names); @@ -233,23 +280,20 @@ public void enumToString() { } /** - * Wrap should handle enums exactly the same way as the JSONObject(Object) - * constructor. + * Wrap should handle enums exactly as a value type like Integer, Boolean, or String. */ @Test public void wrap() { - MyEnum myEnum = MyEnum.VAL2; - JSONObject jsonObject = (JSONObject)JSONObject.wrap(myEnum); - assertTrue("simple enum has no getters", jsonObject.length() == 0); + assertTrue("simple enum has no getters", JSONObject.wrap(MyEnum.VAL2) instanceof MyEnum); MyEnumField myEnumField = MyEnumField.VAL2; - jsonObject = (JSONObject)JSONObject.wrap(myEnumField); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("enum",myEnumField); // validate JSON content Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expected val 2", "val 2".equals(jsonObject.query("/value"))); - assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/intVal"))); + assertTrue("expected 1 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 1); + assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonObject.query("/enum"))); MyEnumClass myEnumClass = new MyEnumClass(); myEnumClass.setMyEnum(MyEnum.VAL1); @@ -259,11 +303,11 @@ public void wrap() { // validate JSON content doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expected 2 myEnumField items", ((Map)(JsonPath.read(doc, "$.myEnumField"))).size() == 2); - assertTrue("expected 0 myEnum items", ((Map)(JsonPath.read(doc, "$.myEnum"))).size() == 0); - assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/myEnumField/intVal"))); - assertTrue("expected val 3", "val 3".equals(jsonObject.query("/myEnumField/value"))); + assertTrue("expected VAL3", "VAL3".equals((JsonPath.read(doc, "$.myEnumField")))); + assertTrue("expected VAL1", "VAL1".equals((JsonPath.read(doc, "$.myEnum")))); + assertTrue("expecting MyEnumField.VAL3", MyEnumField.VAL3.equals(jsonObject.query("/myEnumField"))); + assertTrue("expecting MyEnum.VAL1", MyEnum.VAL1.equals(jsonObject.query("/myEnum"))); } /** @@ -286,6 +330,7 @@ public void enumAPI() { JSONObject jsonObject = new JSONObject(); jsonObject.put("strKey", "value"); + jsonObject.put("strKey2", "VAL1"); jsonObject.put("enumKey", myEnumField); jsonObject.put("enumClassKey", myEnumClass); @@ -321,11 +366,18 @@ public void enumAPI() { // opt with default the wrong value actualEnum = jsonObject.optEnum(MyEnumField.class, "strKey", null); - assertTrue("opt null", actualEnum == null); + assertNull("opt null", actualEnum); + + // opt with default the string value + actualEnum = jsonObject.optEnum(MyEnumField.class, "strKey2", null); + assertEquals(MyEnumField.VAL1, actualEnum); // opt with default an index that does not exist actualEnum = jsonObject.optEnum(MyEnumField.class, "noKey", null); - assertTrue("opt null", actualEnum == null); + assertNull("opt null", actualEnum); + + assertNull("Expected Null when the enum class is null", + jsonObject.optEnum(null, "enumKey")); /** * Exercise the proposed enum API methods on JSONArray diff --git a/src/test/org/json/junit/HTTPTest.java b/src/test/java/org/json/junit/HTTPTest.java similarity index 100% rename from src/test/org/json/junit/HTTPTest.java rename to src/test/java/org/json/junit/HTTPTest.java diff --git a/src/test/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java similarity index 61% rename from src/test/org/json/junit/JSONArrayTest.java rename to src/test/java/org/json/junit/JSONArrayTest.java index 7643ee0..5aef340 100644 --- a/src/test/org/json/junit/JSONArrayTest.java +++ b/src/test/java/org/json/junit/JSONArrayTest.java @@ -1,8 +1,14 @@ package org.json.junit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -25,7 +31,7 @@ * Tests for JSON-Java JSONArray.java */ public class JSONArrayTest { - String arrayStr = + private final String arrayStr = "["+ "true,"+ "false,"+ @@ -49,6 +55,32 @@ public class JSONArrayTest { "\"-1\""+ "]"; + /** + * Tests that the similar method is working as expected. + */ + @Test + public void verifySimilar() { + final String string1 = "HasSameRef"; + JSONArray obj1 = new JSONArray() + .put("abc") + .put(string1) + .put(2); + + JSONArray obj2 = new JSONArray() + .put("abc") + .put(string1) + .put(3); + + JSONArray obj3 = new JSONArray() + .put("abc") + .put(new String(string1)) + .put(2); + + assertFalse("Should eval to false", obj1.similar(obj2)); + + assertTrue("Should eval to true", obj1.similar(obj3)); + } + /** * Attempt to create a JSONArray with a null string. * Expects a NullPointerException. @@ -56,7 +88,7 @@ public class JSONArrayTest { @Test(expected=NullPointerException.class) public void nullException() { String str = null; - new JSONArray(str); + assertNull("Should throw an exception", new JSONArray(str)); } /** @@ -67,12 +99,56 @@ public void nullException() { public void emptStr() { String str = ""; try { - new JSONArray(str); - assertTrue("Should throw an exception", false); + assertNull("Should throw an exception", new JSONArray(str)); } catch (JSONException e) { - assertTrue("Expected an exception message", - "A JSONArray text must start with '[' at 1 [character 2 line 1]". - equals(e.getMessage())); + assertEquals("Expected an exception message", + "A JSONArray text must start with '[' at 0 [character 1 line 1]", + e.getMessage()); + } + } + + /** + * Attempt to create a JSONArray with an unclosed array. + * Expects an exception + */ + @Test + public void unclosedArray() { + try { + assertNull("Should throw an exception", new JSONArray("[")); + } catch (JSONException e) { + assertEquals("Expected an exception message", + "Expected a ',' or ']' at 1 [character 2 line 1]", + e.getMessage()); + } + } + + /** + * Attempt to create a JSONArray with an unclosed array. + * Expects an exception + */ + @Test + public void unclosedArray2() { + try { + assertNull("Should throw an exception", new JSONArray("[\"test\"")); + } catch (JSONException e) { + assertEquals("Expected an exception message", + "Expected a ',' or ']' at 7 [character 8 line 1]", + e.getMessage()); + } + } + + /** + * Attempt to create a JSONArray with an unclosed array. + * Expects an exception + */ + @Test + public void unclosedArray3() { + try { + assertNull("Should throw an exception", new JSONArray("[\"test\",")); + } catch (JSONException e) { + assertEquals("Expected an exception message", + "Expected a ',' or ']' at 8 [character 9 line 1]", + e.getMessage()); } } @@ -85,8 +161,7 @@ public void emptStr() { public void badObject() { String str = "abc"; try { - new JSONArray((Object)str); - assertTrue("Should throw an exception", false); + assertNull("Should throw an exception", new JSONArray((Object)str)); } catch (JSONException e) { assertTrue("Expected an exception message", "JSONArray initial value should be a string or collection or array.". @@ -95,7 +170,7 @@ public void badObject() { } /** - * Verifies that the constructor has backwards compatability with RAW types pre-java5. + * Verifies that the constructor has backwards compatibility with RAW types pre-java5. */ @Test public void verifyConstructor() { @@ -125,7 +200,7 @@ public void verifyConstructor() { } /** - * Verifies that the put Collection has backwards compatability with RAW types pre-java5. + * Verifies that the put Collection has backwards compatibility with RAW types pre-java5. */ @Test public void verifyPutCollection() { @@ -159,7 +234,7 @@ public void verifyPutCollection() { /** - * Verifies that the put Map has backwards compatability with RAW types pre-java5. + * Verifies that the put Map has backwards compatibility with RAW types pre-java5. */ @Test public void verifyPutMap() { @@ -204,9 +279,10 @@ public void verifyPutMap() { * Create a JSONArray doc with a variety of different elements. * Confirm that the values can be accessed via the get[type]() API methods */ + @SuppressWarnings("boxing") @Test public void getArrayValues() { - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(this.arrayStr); // booleans assertTrue("Array true", true == jsonArray.getBoolean(0)); @@ -250,62 +326,62 @@ public void getArrayValues() { */ @Test public void failedGetArrayValues() { - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(this.arrayStr); try { jsonArray.getBoolean(4); assertTrue("expected getBoolean to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[4] is not a boolean.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[4] is not a boolean.",e.getMessage()); } try { jsonArray.get(-1); assertTrue("expected get to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[-1] not found.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[-1] not found.",e.getMessage()); } try { jsonArray.getDouble(4); assertTrue("expected getDouble to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[4] is not a number.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[4] is not a double.",e.getMessage()); } try { jsonArray.getInt(4); assertTrue("expected getInt to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[4] is not a number.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[4] is not a int.",e.getMessage()); } try { jsonArray.getJSONArray(4); assertTrue("expected getJSONArray to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[4] is not a JSONArray.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[4] is not a JSONArray.",e.getMessage()); } try { jsonArray.getJSONObject(4); assertTrue("expected getJSONObject to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[4] is not a JSONObject.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[4] is not a JSONObject.",e.getMessage()); } try { jsonArray.getLong(4); assertTrue("expected getLong to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[4] is not a number.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[4] is not a long.",e.getMessage()); } try { jsonArray.getString(5); assertTrue("expected getString to fail", false); } catch (JSONException e) { - assertTrue("Expected an exception message", - "JSONArray[5] not a string.".equals(e.getMessage())); + assertEquals("Expected an exception message", + "JSONArray[5] is not a String.",e.getMessage()); } } @@ -316,7 +392,7 @@ public void failedGetArrayValues() { */ @Test public void join() { - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(this.arrayStr); String joinStr = jsonArray.join(","); // validate JSON @@ -352,8 +428,8 @@ public void join() { public void length() { assertTrue("expected empty JSONArray length 0", new JSONArray().length() == 0); - JSONArray jsonArray = new JSONArray(arrayStr); - assertTrue("expected JSONArray length 13", jsonArray.length() == 13); + JSONArray jsonArray = new JSONArray(this.arrayStr); + assertTrue("expected JSONArray length 13. instead found "+jsonArray.length(), jsonArray.length() == 13); JSONArray nestedJsonArray = jsonArray.getJSONArray(9); assertTrue("expected JSONArray length 1", nestedJsonArray.length() == 1); } @@ -363,15 +439,19 @@ public void length() { * Confirm that the values can be accessed via the opt[type](index) * and opt[type](index, default) API methods. */ + @SuppressWarnings("boxing") @Test public void opt() { - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(this.arrayStr); assertTrue("Array opt value true", Boolean.TRUE == jsonArray.opt(0)); assertTrue("Array opt value out of range", null == jsonArray.opt(-1)); - assertTrue("Array opt boolean", + assertTrue("Array opt value out of range", + null == jsonArray.opt(jsonArray.length())); + + assertTrue("Array opt boolean", Boolean.TRUE == jsonArray.optBoolean(0)); assertTrue("Array opt boolean default", Boolean.FALSE == jsonArray.optBoolean(-1, Boolean.FALSE)); @@ -385,6 +465,20 @@ public void opt() { assertTrue("Array opt double default implicit", new Double(jsonArray.optDouble(99)).isNaN()); + assertTrue("Array opt float", + new Float(23.45e-4).equals(jsonArray.optFloat(5))); + assertTrue("Array opt float default", + new Float(1).equals(jsonArray.optFloat(0, 1))); + assertTrue("Array opt float default implicit", + new Float(jsonArray.optFloat(99)).isNaN()); + + assertTrue("Array opt Number", + new Double(23.45e-4).equals(jsonArray.optNumber(5))); + assertTrue("Array opt Number default", + new Double(1).equals(jsonArray.optNumber(0, 1d))); + assertTrue("Array opt Number default implicit", + new Double(jsonArray.optNumber(99,Double.NaN).doubleValue()).isNaN()); + assertTrue("Array opt int", new Integer(42).equals(jsonArray.optInt(7))); assertTrue("Array opt int default", @@ -414,11 +508,26 @@ public void opt() { assertTrue("Array opt string default implicit", "".equals(jsonArray.optString(-1))); } + + /** + * Verifies that the opt methods properly convert string values. + */ + @Test + public void optStringConversion(){ + JSONArray ja = new JSONArray("[\"123\",\"true\",\"false\"]"); + assertTrue("unexpected optBoolean value",ja.optBoolean(1,false)==true); + assertTrue("unexpected optBoolean value",ja.optBoolean(2,true)==false); + assertTrue("unexpected optInt value",ja.optInt(0,0)==123); + assertTrue("unexpected optLong value",ja.optLong(0,0)==123); + assertTrue("unexpected optDouble value",ja.optDouble(0,0.0)==123.0); + assertTrue("unexpected optBigInteger value",ja.optBigInteger(0,BigInteger.ZERO).compareTo(new BigInteger("123"))==0); + assertTrue("unexpected optBigDecimal value",ja.optBigDecimal(0,BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0); } /** * Exercise the JSONArray.put(value) method with various parameters * and confirm the resulting JSONArray. */ + @SuppressWarnings("boxing") @Test public void put() { JSONArray jsonArray = new JSONArray(); @@ -494,6 +603,7 @@ public void put() { * Exercise the JSONArray.put(index, value) method with various parameters * and confirm the resulting JSONArray. */ + @SuppressWarnings("boxing") @Test public void putIndex() { JSONArray jsonArray = new JSONArray(); @@ -574,14 +684,14 @@ public void putIndex() { */ @Test public void remove() { - String arrayStr = + String arrayStr1 = "["+ "1"+ "]"; - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(arrayStr1); jsonArray.remove(0); assertTrue("array should be empty", null == jsonArray.remove(5)); - assertTrue("jsonArray should be empty", jsonArray.length() == 0); + assertTrue("jsonArray should be empty", jsonArray.isEmpty()); } /** @@ -590,11 +700,11 @@ public void remove() { */ @Test public void notSimilar() { - String arrayStr = + String arrayStr1 = "["+ "1"+ "]"; - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(arrayStr1); JSONArray otherJsonArray = new JSONArray(); assertTrue("arrays lengths differ", !jsonArray.similar(otherJsonArray)); @@ -623,6 +733,71 @@ public void notSimilar() { !jsonArray.similar(otherJsonArray)); } + /** + * Exercise JSONArray toString() method with various indent levels. + */ + @Test + public void jsonArrayToStringIndent() { + String jsonArray0Str = + "[" + + "[1,2," + + "{\"key3\":true}" + + "]," + + "{\"key1\":\"val1\",\"key2\":" + + "{\"key2\":\"val2\"}" + + "}," + + "[" + + "[1,2.1]" + + "," + + "[null]" + + "]" + + "]"; + + String jsonArray1Str = + "[\n" + + " [\n" + + " 1,\n" + + " 2,\n" + + " {\"key3\": true}\n" + + " ],\n" + + " {\n" + + " \"key1\": \"val1\",\n" + + " \"key2\": {\"key2\": \"val2\"}\n" + + " },\n" + + " [\n" + + " [\n" + + " 1,\n" + + " 2.1\n" + + " ],\n" + + " [null]\n" + + " ]\n" + + "]"; + String jsonArray4Str = + "[\n" + + " [\n" + + " 1,\n" + + " 2,\n" + + " {\"key3\": true}\n" + + " ],\n" + + " {\n" + + " \"key1\": \"val1\",\n" + + " \"key2\": {\"key2\": \"val2\"}\n" + + " },\n" + + " [\n" + + " [\n" + + " 1,\n" + + " 2.1\n" + + " ],\n" + + " [null]\n" + + " ]\n" + + "]"; + JSONArray jsonArray = new JSONArray(jsonArray0Str); + assertEquals(jsonArray0Str, jsonArray.toString()); + assertEquals(jsonArray0Str, jsonArray.toString(0)); + assertEquals(jsonArray1Str, jsonArray.toString(1)); + assertEquals(jsonArray4Str, jsonArray.toString(4)); + } + /** * Convert an empty JSONArray to JSONObject */ @@ -658,9 +833,10 @@ public void objectArrayVsIsArray() { /** * Exercise the JSONArray iterator. */ + @SuppressWarnings("boxing") @Test public void iterator() { - JSONArray jsonArray = new JSONArray(arrayStr); + JSONArray jsonArray = new JSONArray(this.arrayStr); Iterator it = jsonArray.iterator(); assertTrue("Array true", Boolean.TRUE.equals(it.next())); @@ -710,4 +886,188 @@ public void optQueryWithNoResult() { public void optQueryWithSyntaxError() { new JSONArray().optQuery("invalid"); } + + + /** + * Exercise the JSONArray write() method + */ + @Test + public void write() throws IOException { + String str = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":2,\"key3\":3}]"; + JSONArray jsonArray = new JSONArray(str); + String expectedStr = str; + StringWriter stringWriter = new StringWriter(); + try { + jsonArray.write(stringWriter); + String actualStr = stringWriter.toString(); + assertTrue("write() expected " + expectedStr + + " but found " + actualStr, + expectedStr.equals(actualStr)); + } finally { + stringWriter.close(); + } + } + + /** + * Exercise the JSONArray write() method using Appendable. + */ +/* + @Test + public void writeAppendable() { + String str = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":2,\"key3\":3}]"; + JSONArray jsonArray = new JSONArray(str); + String expectedStr = str; + StringBuilder stringBuilder = new StringBuilder(); + Appendable appendable = jsonArray.write(stringBuilder); + String actualStr = appendable.toString(); + assertTrue("write() expected " + expectedStr + + " but found " + actualStr, + expectedStr.equals(actualStr)); + } +*/ + + /** + * Exercise the JSONArray write(Writer, int, int) method + */ + @Test + public void write3Param() throws IOException { + String str0 = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":false,\"key3\":3.14}]"; + String str2 = + "[\n" + + " \"value1\",\n" + + " \"value2\",\n" + + " {\n" + + " \"key1\": 1,\n" + + " \"key2\": false,\n" + + " \"key3\": 3.14\n" + + " }\n" + + " ]"; + JSONArray jsonArray = new JSONArray(str0); + String expectedStr = str0; + StringWriter stringWriter = new StringWriter(); + try { + String actualStr = jsonArray.write(stringWriter, 0, 0).toString(); + assertEquals(expectedStr, actualStr); + } finally { + stringWriter.close(); + } + stringWriter = new StringWriter(); + try { + expectedStr = str2; + String actualStr = jsonArray.write(stringWriter, 2, 1).toString(); + assertEquals(expectedStr, actualStr); + } finally { + stringWriter.close(); + } + } + + /** + * Exercise the JSONArray write(Appendable, int, int) method + */ +/* + @Test + public void write3ParamAppendable() { + String str0 = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":false,\"key3\":3.14}]"; + String str2 = + "[\n" + + " \"value1\",\n" + + " \"value2\",\n" + + " {\n" + + " \"key1\": 1,\n" + + " \"key2\": false,\n" + + " \"key3\": 3.14\n" + + " }\n" + + " ]"; + JSONArray jsonArray = new JSONArray(str0); + String expectedStr = str0; + StringBuilder stringBuilder = new StringBuilder(); + Appendable appendable = jsonArray.write(stringBuilder, 0, 0); + String actualStr = appendable.toString(); + assertEquals(expectedStr, actualStr); + + expectedStr = str2; + stringBuilder = new StringBuilder(); + appendable = jsonArray.write(stringBuilder, 2, 1); + actualStr = appendable.toString(); + assertEquals(expectedStr, actualStr); + } +*/ + + /** + * Exercise JSONArray toString() method with various indent levels. + */ + @Test + public void toList() { + String jsonArrayStr = + "[" + + "[1,2," + + "{\"key3\":true}" + + "]," + + "{\"key1\":\"val1\",\"key2\":" + + "{\"key2\":null}," + + "\"key3\":42,\"key4\":[]" + + "}," + + "[" + + "[\"value1\",2.1]" + + "," + + "[null]" + + "]" + + "]"; + + JSONArray jsonArray = new JSONArray(jsonArrayStr); + List list = jsonArray.toList(); + + assertTrue("List should not be null", list != null); + assertTrue("List should have 3 elements", list.size() == 3); + + List val1List = (List) list.get(0); + assertTrue("val1 should not be null", val1List != null); + assertTrue("val1 should have 3 elements", val1List.size() == 3); + + assertTrue("val1 value 1 should be 1", val1List.get(0).equals(Integer.valueOf(1))); + assertTrue("val1 value 2 should be 2", val1List.get(1).equals(Integer.valueOf(2))); + + Map key1Value3Map = (Map)val1List.get(2); + assertTrue("Map should not be null", key1Value3Map != null); + assertTrue("Map should have 1 element", key1Value3Map.size() == 1); + assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE)); + + Map val2Map = (Map) list.get(1); + assertTrue("val2 should not be null", val2Map != null); + assertTrue("val2 should have 4 elements", val2Map.size() == 4); + assertTrue("val2 map key 1 should be val1", val2Map.get("key1").equals("val1")); + assertTrue("val2 map key 3 should be 42", val2Map.get("key3").equals(Integer.valueOf(42))); + + Map val2Key2Map = (Map)val2Map.get("key2"); + assertTrue("val2 map key 2 should not be null", val2Key2Map != null); + assertTrue("val2 map key 2 should have an entry", val2Key2Map.containsKey("key2")); + assertTrue("val2 map key 2 value should be null", val2Key2Map.get("key2") == null); + + List val2Key4List = (List)val2Map.get("key4"); + assertTrue("val2 map key 4 should not be null", val2Key4List != null); + assertTrue("val2 map key 4 should be empty", val2Key4List.isEmpty()); + + List val3List = (List) list.get(2); + assertTrue("val3 should not be null", val3List != null); + assertTrue("val3 should have 2 elements", val3List.size() == 2); + + List val3Val1List = (List)val3List.get(0); + assertTrue("val3 list val 1 should not be null", val3Val1List != null); + assertTrue("val3 list val 1 should have 2 elements", val3Val1List.size() == 2); + assertTrue("val3 list val 1 list element 1 should be value1", val3Val1List.get(0).equals("value1")); + assertTrue("val3 list val 1 list element 2 should be 2.1", val3Val1List.get(1).equals(Double.valueOf("2.1"))); + + List val3Val2List = (List)val3List.get(1); + assertTrue("val3 list val 2 should not be null", val3Val2List != null); + assertTrue("val3 list val 2 should have 1 element", val3Val2List.size() == 1); + assertTrue("val3 list val 2 list element 1 should be null", val3Val2List.get(0) == null); + + // assert that toList() is a deep copy + jsonArray.getJSONObject(1).put("key1", "still val1"); + assertTrue("val2 map key 1 should be val1", val2Map.get("key1").equals("val1")); + + // assert that the new list is mutable + assertTrue("Removing an entry should succeed", list.remove(2) != null); + assertTrue("List should have 2 elements", list.size() == 2); + } } diff --git a/src/test/org/json/junit/JSONMLTest.java b/src/test/java/org/json/junit/JSONMLTest.java similarity index 71% rename from src/test/org/json/junit/JSONMLTest.java rename to src/test/java/org/json/junit/JSONMLTest.java index 953a39d..6f04fd5 100644 --- a/src/test/org/json/junit/JSONMLTest.java +++ b/src/test/java/org/json/junit/JSONMLTest.java @@ -42,11 +42,11 @@ public void emptyXMLException() { String xmlStr = ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Bad XML at 1 [character 2 line 1]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Bad XML at 0 [character 1 line 1]", + e.getMessage()); } } @@ -95,11 +95,11 @@ public void nonXMLException() { String xmlStr = "{ \"this is\": \"not xml\"}"; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Bad XML at 25 [character 26 line 1]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Bad XML at 23 [character 24 line 1]", + e.getMessage()); } } @@ -133,9 +133,9 @@ public void emptyTagException() { JSONML.toString(jsonArray); assertTrue("Expecting an exception", false); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONArray[0] not a string.". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "JSONArray[0] is not a String.", + e.getMessage()); } } @@ -198,11 +198,11 @@ public void invalidSlashInTagException() { ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misshaped tag at 176 [character 14 line 7]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misshaped tag at 176 [character 14 line 4]", + e.getMessage()); } } @@ -223,11 +223,11 @@ public void invalidBangInTagException() { ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misshaped meta tag at 216 [character 13 line 11]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misshaped meta tag at 215 [character 12 line 7]", + e.getMessage()); } } @@ -253,11 +253,11 @@ public void invalidBangNoCloseInTagException() { ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misshaped meta tag at 215 [character 13 line 11]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misshaped meta tag at 214 [character 12 line 7]", + e.getMessage()); } } @@ -283,11 +283,11 @@ public void noCloseStartTagException() { ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misplaced '<' at 194 [character 5 line 10]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misplaced '<' at 194 [character 5 line 6]", + e.getMessage()); } } @@ -343,11 +343,11 @@ public void noCloseEndBraceException() { ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misplaced '<' at 206 [character 1 line 12]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misplaced '<' at 206 [character 1 line 7]", + e.getMessage()); } } @@ -373,11 +373,11 @@ public void invalidCDATABangInTagException() { ""; try { JSONML.toJSONArray(xmlStr); - assertTrue("Expecting an exception", false); + fail("Expecting an exception"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Expected 'CDATA[' at 204 [character 11 line 9]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Expected 'CDATA[' at 204 [character 11 line 5]", + e.getMessage()); } } @@ -698,4 +698,135 @@ public void commentsInXML() { Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray); } + /** + * JSON string with lost leading zero and converted "True" to true. See test + * result in comment below. + */ + @Test + public void testToJSONArray_jsonOutput() { + final String originalXml = "011000True"; + final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",1],[\"id\",\"00\"],[\"id\",0],[\"item\",{\"id\":\"01\"}],[\"title\",true]]"; + final JSONArray actualJsonOutput = JSONML.toJSONArray(originalXml, false); + assertEquals(expectedJsonString, actualJsonOutput.toString()); + } + + /** + * JSON string cannot be reverted to original xml when type guessing is used. + */ + @Test + public void testToJSONArray_reversibility() { + final String originalXml = "011000True"; + final String revertedXml = JSONML.toString(JSONML.toJSONArray(originalXml, false)); + assertNotEquals(revertedXml, originalXml); + } + + /** + * JSON string cannot be reverted to original xml when type guessing is used. + * When we force all the values as string, the original text comes back. + */ + @Test + public void testToJSONArray_reversibility2() { + final String originalXml = "011000True"; + final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",\"1\"],[\"id\",\"00\"],[\"id\",\"0\"],[\"item\",{\"id\":\"01\"}],[\"title\",\"True\"]]"; + final JSONArray json = JSONML.toJSONArray(originalXml,true); + assertEquals(expectedJsonString, json.toString()); + + final String reverseXml = JSONML.toString(json); + assertEquals(originalXml, reverseXml); + } + + /** + * JSON can be reverted to original xml. + */ + @Test + public void testToJSONArray_reversibility3() { + final String originalXml = "400402"; + final JSONArray jsonArray = JSONML.toJSONArray(originalXml, false); + final String revertedXml = JSONML.toString(jsonArray); + assertEquals(revertedXml, originalXml); + } + + /** + * JSON string cannot be reverted to original xml. See test result in + * comment below. + */ + @Test + public void testToJSONObject_reversibility() { + final String originalXml = "400402"; + final JSONObject originalObject=JSONML.toJSONObject(originalXml,false); + final String originalJson = originalObject.toString(); + final String xml = JSONML.toString(originalObject); + final JSONObject revertedObject = JSONML.toJSONObject(xml, false); + final String newJson = revertedObject.toString(); + assertTrue("JSON Objects are not similar",originalObject.similar(revertedObject)); + assertEquals("original JSON does not equal the new JSON",originalJson, newJson); + } + +// these tests do not pass for the following reasons: +// 1. Our XML parser does not handle generic HTML entities, only valid XML entities. Hence   +// or other HTML specific entities would fail on reversability +// 2. Our JSON implementation for storing the XML attributes uses the standard unordered map. +// This means that can not be reversed reliably. +// +// /** +// * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't. +// * Technically JsonML should be able to transform any valid xhtml document, but ours only supports +// * standard XML entities, not HTML entities. +// */ +// @Test +// public void testAttributeConversionReversabilityHTML() { +// final String originalXml = "
#5D28D1Example text here
#AF44EF127310656
#AAD034 © 
"; +// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"\u00A0\",[\"span\",{ \"style\" : \"background-color:maroon\" },\"\u00A9\"],\"\u00A0\"]]]"; +// final JSONArray json = JSONML.toJSONArray(originalXml,true); +// final String actualJsonString = json.toString(); +// +// final String reverseXml = JSONML.toString(json); +// assertNotEquals(originalXml, reverseXml); +// +// assertNotEquals(expectedJsonString, actualJsonString); +// } +// +// /** +// * Test texts taken from jsonml.org but modified to have XML entities only. +// */ +// @Test +// public void testAttributeConversionReversabilityXML() { +// final String originalXml = "
#5D28D1Example text here
#AF44EF127310656
#AAD034&><
"; +// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"&\",[\"span\",{ \"style\" : \"background-color:maroon\" },\">\"],\"<\"]]]"; +// final JSONArray jsonML = JSONML.toJSONArray(originalXml,true); +// final String actualJsonString = jsonML.toString(); +// +// final String reverseXml = JSONML.toString(jsonML); +// // currently not equal because the hashing of the attribute objects makes the attribute +// // order not happen the same way twice +// assertEquals(originalXml, reverseXml); +// +// assertEquals(expectedJsonString, actualJsonString); +// } + + @Test (timeout = 6000) + public void testIssue484InfinteLoop1() { + try { + JSONML.toJSONObject("??*^M??|?CglR^F??`??>?w??PIlr^E??D^X^]?$?-^R?o??O?*??{OD?^FY??`2a????NM?b^Tq?:O?>S$^K?J?^FB.gUK?m^H??zE??^??!v]?^A???^[^A??^U?c??????h???s???g^Z???`?q^Dbi??:^QZl?)?}1^??k?0??:$V?$?Ovs(}J??^V????2;^QgQ?^_^A?^D?^U?Tg?K?`?h%c?hmGA??w??PIlr??D?$?-?o??O?*??{OD?Y??`2a????NM?bq?:O?>S$ ?J?B.gUK?m\b??zE???!v]???????c??????h???s???g???`?qbi??:Zl?)?}1^??k?0??:$V?$?Ovs(}J??????2;gQ????Tg?K?`?h%c?hmGA?)(JsonPath.read(doc, "$"))).size() == 4); - assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObjectByName.query("/falseKey"))); - assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey"))); - assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObjectByName.query("/stringKey"))); - assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObjectByName.query("/doubleKey"))); - } - - /** - * JSONObjects can be built from a Map. - * In this test the map is null. - * the JSONObject(JsonTokener) ctor is not tested directly since it already - * has full coverage from other tests. - */ - @Test - public void jsonObjectByNullMap() { - Map map = null; - JSONObject jsonObject = new JSONObject(map); - assertTrue("jsonObject should be empty", jsonObject.length() == 0); - } - - /** - * JSONObjects can be built from a Map. - * In this test all of the map entries are valid JSON types. - */ - @Test - public void jsonObjectByMap() { - Map map = new HashMap(); - map.put("trueKey", new Boolean(true)); - map.put("falseKey", new Boolean(false)); - map.put("stringKey", "hello world!"); - map.put("escapeStringKey", "h\be\tllo w\u1234orld!"); - map.put("intKey", new Long(42)); - map.put("doubleKey", new Double(-23.45e67)); - JSONObject jsonObject = new JSONObject(map); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 6 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 6); - assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); - assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); - assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey"))); - assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey"))); - assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey"))); - } - - /** - * Verifies that the constructor has backwards compatability with RAW types pre-java5. - */ - @Test - public void verifyConstructor() { - - final JSONObject expected = new JSONObject("{\"myKey\":10}"); - - @SuppressWarnings("rawtypes") - Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10)); - JSONObject jaRaw = new JSONObject(myRawC); - - Map myCStrObj = Collections.singletonMap("myKey", - (Object) Integer.valueOf(10)); - JSONObject jaStrObj = new JSONObject(myCStrObj); - - Map myCStrInt = Collections.singletonMap("myKey", - Integer.valueOf(10)); - JSONObject jaStrInt = new JSONObject(myCStrInt); - - Map myCObjObj = Collections.singletonMap((Object) "myKey", - (Object) Integer.valueOf(10)); - JSONObject jaObjObj = new JSONObject(myCObjObj); - - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaRaw)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaStrObj)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaStrInt)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaObjObj)); - } - - /** - * Verifies that the put Collection has backwards compatability with RAW types pre-java5. - */ - @Test - public void verifyPutCollection() { - - final JSONObject expected = new JSONObject("{\"myCollection\":[10]}"); - - @SuppressWarnings("rawtypes") - Collection myRawC = Collections.singleton(Integer.valueOf(10)); - JSONObject jaRaw = new JSONObject(); - jaRaw.put("myCollection", myRawC); - - Collection myCObj = Collections.singleton((Object) Integer - .valueOf(10)); - JSONObject jaObj = new JSONObject(); - jaObj.put("myCollection", myCObj); - - Collection myCInt = Collections.singleton(Integer - .valueOf(10)); - JSONObject jaInt = new JSONObject(); - jaInt.put("myCollection", myCInt); - - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaRaw)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaObj)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaInt)); - } - - - /** - * Verifies that the put Map has backwards compatability with RAW types pre-java5. - */ - @Test - public void verifyPutMap() { - - final JSONObject expected = new JSONObject("{\"myMap\":{\"myKey\":10}}"); - - @SuppressWarnings("rawtypes") - Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10)); - JSONObject jaRaw = new JSONObject(); - jaRaw.put("myMap", myRawC); - - Map myCStrObj = Collections.singletonMap("myKey", - (Object) Integer.valueOf(10)); - JSONObject jaStrObj = new JSONObject(); - jaStrObj.put("myMap", myCStrObj); - - Map myCStrInt = Collections.singletonMap("myKey", - Integer.valueOf(10)); - JSONObject jaStrInt = new JSONObject(); - jaStrInt.put("myMap", myCStrInt); - - Map myCObjObj = Collections.singletonMap((Object) "myKey", - (Object) Integer.valueOf(10)); - JSONObject jaObjObj = new JSONObject(); - jaObjObj.put("myMap", myCObjObj); - - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaRaw)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaStrObj)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaStrInt)); - assertTrue( - "The RAW Collection should give me the same as the Typed Collection", - expected.similar(jaObjObj)); - } - - - /** - * JSONObjects can be built from a Map. - * In this test the map entries are not valid JSON types. - * The actual conversion is kind of interesting. - */ - @Test - public void jsonObjectByMapWithUnsupportedValues() { - Map jsonMap = new HashMap(); - // Just insert some random objects - jsonMap.put("key1", new CDL()); - jsonMap.put("key2", new Exception()); - - JSONObject jsonObject = new JSONObject(jsonMap); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expected 0 key1 items", ((Map)(JsonPath.read(doc, "$.key1"))).size() == 0); - assertTrue("expected \"key2\":java.lang.Exception","java.lang.Exception".equals(jsonObject.query("/key2"))); - } - - /** - * JSONObjects can be built from a Map. - * In this test one of the map values is null - */ - @Test - public void jsonObjectByMapWithNullValue() { - Map map = new HashMap(); - map.put("trueKey", new Boolean(true)); - map.put("falseKey", new Boolean(false)); - map.put("stringKey", "hello world!"); - map.put("nullKey", null); - map.put("escapeStringKey", "h\be\tllo w\u1234orld!"); - map.put("intKey", new Long(42)); - map.put("doubleKey", new Double(-23.45e67)); - JSONObject jsonObject = new JSONObject(map); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 6 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 6); - assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); - assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); - assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey"))); - assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey"))); - assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey"))); - assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey"))); - } - - /** - * JSONObject built from a bean. In this case all but one of the - * bean getters return valid JSON types - */ - @Test - public void jsonObjectByBean() { - /** - * Default access classes have to be mocked since JSONObject, which is - * not in the same package, cannot call MyBean methods by reflection. - */ - MyBean myBean = mock(MyBean.class); - when(myBean.getDoubleKey()).thenReturn(-23.45e7); - when(myBean.getIntKey()).thenReturn(42); - when(myBean.getStringKey()).thenReturn("hello world!"); - when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!"); - when(myBean.isTrueKey()).thenReturn(true); - when(myBean.isFalseKey()).thenReturn(false); - when(myBean.getStringReaderKey()).thenReturn( - new StringReader("") { - }); - - JSONObject jsonObject = new JSONObject(myBean); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 8 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 8); - assertTrue("expected 0 items in stringReaderKey", ((Map) (JsonPath.read(doc, "$.stringReaderKey"))).size() == 0); - assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); - assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); - assertTrue("expected hello world!","hello world!".equals(jsonObject.query("/stringKey"))); - assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey"))); - assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey"))); - assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey"))); - // sorry, mockito artifact - assertTrue("expected 2 callbacks items", ((List)(JsonPath.read(doc, "$.callbacks"))).size() == 2); - assertTrue("expected 0 handler items", ((Map)(JsonPath.read(doc, "$.callbacks[0].handler"))).size() == 0); - assertTrue("expected 0 callbacks[1] items", ((Map)(JsonPath.read(doc, "$.callbacks[1]"))).size() == 0); - } - - /** - * A bean is also an object. But in order to test the JSONObject - * ctor that takes an object and a list of names, - * this particular bean needs some public - * data members, which have been added to the class. - */ - @Test - public void jsonObjectByObjectAndNames() { - String[] keys = {"publicString", "publicInt"}; - // just need a class that has public data members - MyPublicClass myPublicClass = new MyPublicClass(); - JSONObject jsonObject = new JSONObject(myPublicClass, keys); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString"))); - assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt"))); - } - - /** - * Exercise the JSONObject from resource bundle functionality. - * The test resource bundle is uncomplicated, but provides adequate test coverage. - */ - @Test - public void jsonObjectByResourceBundle() { - JSONObject jsonObject = new - JSONObject("org.json.junit.StringsResourceBundle", - Locale.getDefault()); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); - assertTrue("expected 2 greetings items", ((Map)(JsonPath.read(doc, "$.greetings"))).size() == 2); - assertTrue("expected \"hello\":\"Hello, \"", "Hello, ".equals(jsonObject.query("/greetings/hello"))); - assertTrue("expected \"world\":\"World!\"", "World!".equals(jsonObject.query("/greetings/world"))); - assertTrue("expected 2 farewells items", ((Map)(JsonPath.read(doc, "$.farewells"))).size() == 2); - assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later"))); - assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator"))); - } - - /** - * Exercise the JSONObject.accumulate() method - */ - @Test - public void jsonObjectAccumulate() { - - JSONObject jsonObject = new JSONObject(); - jsonObject.accumulate("myArray", true); - jsonObject.accumulate("myArray", false); - jsonObject.accumulate("myArray", "hello world!"); - jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!"); - jsonObject.accumulate("myArray", 42); - jsonObject.accumulate("myArray", -23.45e7); - // include an unsupported object for coverage - try { - jsonObject.accumulate("myArray", Double.NaN); - assertTrue("Expected exception", false); - } catch (JSONException ignored) {} - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); - assertTrue("expected 6 myArray items", ((List)(JsonPath.read(doc, "$.myArray"))).size() == 6); - assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0"))); - assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1"))); - assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2"))); - assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3"))); - assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4"))); - assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5"))); - } - - /** - * Exercise the JSONObject append() functionality - */ - @Test - public void jsonObjectAppend() { - JSONObject jsonObject = new JSONObject(); - jsonObject.append("myArray", true); - jsonObject.append("myArray", false); - jsonObject.append("myArray", "hello world!"); - jsonObject.append("myArray", "h\be\tllo w\u1234orld!"); - jsonObject.append("myArray", 42); - jsonObject.append("myArray", -23.45e7); - // include an unsupported object for coverage - try { - jsonObject.append("myArray", Double.NaN); - assertTrue("Expected exception", false); - } catch (JSONException ignored) {} - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); - assertTrue("expected 6 myArray items", ((List)(JsonPath.read(doc, "$.myArray"))).size() == 6); - assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0"))); - assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1/"))); - assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2"))); - assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3"))); - assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4"))); - assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5"))); - } - - /** - * Exercise the JSONObject doubleToString() method - */ - @Test - public void jsonObjectDoubleToString() { - String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" }; - Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67, - Double.NaN, Double.NEGATIVE_INFINITY }; - for (int i = 0; i < expectedStrs.length; ++i) { - String actualStr = JSONObject.doubleToString(doubles[i]); - assertTrue("value expected ["+expectedStrs[i]+ - "] found ["+actualStr+ "]", - expectedStrs[i].equals(actualStr)); - } - } - - /** - * Exercise some JSONObject get[type] and opt[type] methods - */ - @Test - public void jsonObjectValues() { - String str = - "{"+ - "\"trueKey\":true,"+ - "\"falseKey\":false,"+ - "\"trueStrKey\":\"true\","+ - "\"falseStrKey\":\"false\","+ - "\"stringKey\":\"hello world!\","+ - "\"intKey\":42,"+ - "\"intStrKey\":\"43\","+ - "\"longKey\":1234567890123456789,"+ - "\"longStrKey\":\"987654321098765432\","+ - "\"doubleKey\":-23.45e7,"+ - "\"doubleStrKey\":\"00001.000\","+ - "\"arrayKey\":[0,1,2],"+ - "\"objectKey\":{\"myKey\":\"myVal\"}"+ - "}"; - JSONObject jsonObject = new JSONObject(str); - assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey")); - assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey")); - assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey")); - assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey")); - assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey")); - assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey")); - assertTrue("stringKey should be string", - jsonObject.getString("stringKey").equals("hello world!")); - assertTrue("doubleKey should be double", - jsonObject.getDouble("doubleKey") == -23.45e7); - assertTrue("doubleStrKey should be double", - jsonObject.getDouble("doubleStrKey") == 1); - assertTrue("opt doubleKey should be double", - jsonObject.optDouble("doubleKey") == -23.45e7); - assertTrue("opt doubleKey with Default should be double", - jsonObject.optDouble("doubleStrKey", Double.NaN) == 1); - assertTrue("intKey should be int", - jsonObject.optInt("intKey") == 42); - assertTrue("opt intKey should be int", - jsonObject.optInt("intKey", 0) == 42); - assertTrue("opt intKey with default should be int", - jsonObject.getInt("intKey") == 42); - assertTrue("intStrKey should be int", - jsonObject.getInt("intStrKey") == 43); - assertTrue("longKey should be long", - jsonObject.getLong("longKey") == 1234567890123456789L); - assertTrue("opt longKey should be long", - jsonObject.optLong("longKey") == 1234567890123456789L); - assertTrue("opt longKey with default should be long", - jsonObject.optLong("longKey", 0) == 1234567890123456789L); - assertTrue("longStrKey should be long", - jsonObject.getLong("longStrKey") == 987654321098765432L); - assertTrue("xKey should not exist", - jsonObject.isNull("xKey")); - assertTrue("stringKey should exist", - jsonObject.has("stringKey")); - assertTrue("opt stringKey should string", - jsonObject.optString("stringKey").equals("hello world!")); - assertTrue("opt stringKey with default should string", - jsonObject.optString("stringKey", "not found").equals("hello world!")); - JSONArray jsonArray = jsonObject.getJSONArray("arrayKey"); - assertTrue("arrayKey should be JSONArray", - jsonArray.getInt(0) == 0 && - jsonArray.getInt(1) == 1 && - jsonArray.getInt(2) == 2); - jsonArray = jsonObject.optJSONArray("arrayKey"); - assertTrue("opt arrayKey should be JSONArray", - jsonArray.getInt(0) == 0 && - jsonArray.getInt(1) == 1 && - jsonArray.getInt(2) == 2); - JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey"); - assertTrue("objectKey should be JSONObject", - jsonObjectInner.get("myKey").equals("myVal")); - } - - /** - * Check whether JSONObject handles large or high precision numbers correctly - */ - @Test - public void stringToValueNumbersTest() { - assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double); - assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double); - assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String); - assertTrue( "0.2 should be a Double!", - JSONObject.stringToValue( "0.2" ) instanceof Double ); - assertTrue( "Doubles should be Doubles, even when incorrectly converting floats!", - JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof Double ); - /** - * This test documents a need for BigDecimal conversion. - */ - Object obj = JSONObject.stringToValue( "299792.457999999984" ); - assertTrue( "evaluates to 299792.458 doubld instead of 299792.457999999984 BigDecimal!", - obj.equals(new Double(299792.458)) ); - assertTrue( "1 should be an Integer!", - JSONObject.stringToValue( "1" ) instanceof Integer ); - assertTrue( "Integer.MAX_VALUE should still be an Integer!", - JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer ); - assertTrue( "Large integers should be a Long!", - JSONObject.stringToValue( new Long( Long.sum( Integer.MAX_VALUE, 1 ) ).toString() ) instanceof Long ); - assertTrue( "Long.MAX_VALUE should still be an Integer!", - JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long ); - - String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString(); - assertTrue( "Really large integers currently evaluate to string", - JSONObject.stringToValue(str).equals("9223372036854775808")); - } - - /** - * This test documents numeric values which could be numerically - * handled as BigDecimal or BigInteger. It helps determine what outputs - * will change if those types are supported. - */ - @Test - public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() { - // Valid JSON Numbers, probably should return BigDecimal or BigInteger objects - String str = - "{"+ - "\"numberWithDecimals\":299792.457999999984,"+ - "\"largeNumber\":12345678901234567890,"+ - "\"preciseNumber\":0.2000000000000000111,"+ - "\"largeExponent\":-23.45e2327"+ - "}"; - JSONObject jsonObject = new JSONObject(str); - // Comes back as a double, but loses precision - assertTrue( "numberWithDecimals currently evaluates to double 299792.458", - jsonObject.get( "numberWithDecimals" ).equals( new Double( "299792.458" ) ) ); - Object obj = jsonObject.get( "largeNumber" ); - assertTrue("largeNumber currently evaluates to string", - "12345678901234567890".equals(obj)); - // comes back as a double but loses precision - assertTrue( "preciseNumber currently evaluates to double 0.2", - jsonObject.get( "preciseNumber" ).equals(new Double(0.2))); - obj = jsonObject.get( "largeExponent" ); - assertTrue("largeExponent should currently evaluates as a string", - "-23.45e2327".equals(obj)); - } - - /** - * This test documents how JSON-Java handles invalid numeric input. - */ - @Test - public void jsonInvalidNumberValues() { - // Number-notations supported by Java and invalid as JSON - String str = - "{"+ - "\"hexNumber\":-0x123,"+ - "\"tooManyZeros\":00,"+ - "\"negativeInfinite\":-Infinity,"+ - "\"negativeNaN\":-NaN,"+ - "\"negativeFraction\":-.01,"+ - "\"tooManyZerosFraction\":00.001,"+ - "\"negativeHexFloat\":-0x1.fffp1,"+ - "\"hexFloat\":0x1.0P-1074,"+ - "\"floatIdentifier\":0.1f,"+ - "\"doubleIdentifier\":0.1d"+ - "}"; - JSONObject jsonObject = new JSONObject(str); - Object obj; - obj = jsonObject.get( "hexNumber" ); - assertFalse( "hexNumber must not be a number (should throw exception!?)", - obj instanceof Number ); - assertTrue("hexNumber currently evaluates to string", - obj.equals("-0x123")); - assertTrue( "tooManyZeros currently evaluates to string", - jsonObject.get( "tooManyZeros" ).equals("00")); - obj = jsonObject.get("negativeInfinite"); - assertTrue( "negativeInfinite currently evaluates to string", - obj.equals("-Infinity")); - obj = jsonObject.get("negativeNaN"); - assertTrue( "negativeNaN currently evaluates to string", - obj.equals("-NaN")); - assertTrue( "negativeFraction currently evaluates to double -0.01", - jsonObject.get( "negativeFraction" ).equals(new Double(-0.01))); - assertTrue( "tooManyZerosFraction currently evaluates to double 0.001", - jsonObject.get( "tooManyZerosFraction" ).equals(new Double(0.001))); - assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875", - jsonObject.get( "negativeHexFloat" ).equals(new Double(-3.99951171875))); - assertTrue("hexFloat currently evaluates to double 4.9E-324", - jsonObject.get("hexFloat").equals(new Double(4.9E-324))); - assertTrue("floatIdentifier currently evaluates to double 0.1", - jsonObject.get("floatIdentifier").equals(new Double(0.1))); - assertTrue("doubleIdentifier currently evaluates to double 0.1", - jsonObject.get("doubleIdentifier").equals(new Double(0.1))); - } - - /** - * Tests how JSONObject get[type] handles incorrect types - */ - @Test - public void jsonObjectNonAndWrongValues() { - String str = - "{"+ - "\"trueKey\":true,"+ - "\"falseKey\":false,"+ - "\"trueStrKey\":\"true\","+ - "\"falseStrKey\":\"false\","+ - "\"stringKey\":\"hello world!\","+ - "\"intKey\":42,"+ - "\"intStrKey\":\"43\","+ - "\"longKey\":1234567890123456789,"+ - "\"longStrKey\":\"987654321098765432\","+ - "\"doubleKey\":-23.45e7,"+ - "\"doubleStrKey\":\"00001.000\","+ - "\"arrayKey\":[0,1,2],"+ - "\"objectKey\":{\"myKey\":\"myVal\"}"+ - "}"; - JSONObject jsonObject = new JSONObject(str); - try { - jsonObject.getBoolean("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("expecting an exception message", - "JSONObject[\"nonKey\"] not found.".equals(e.getMessage())); - } - try { - jsonObject.getBoolean("stringKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"stringKey\"] is not a Boolean.". - equals(e.getMessage())); - } - try { - jsonObject.getString("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"nonKey\"] not found.". - equals(e.getMessage())); - } - try { - jsonObject.getString("trueKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"trueKey\"] not a string.". - equals(e.getMessage())); - } - try { - jsonObject.getDouble("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"nonKey\"] not found.". - equals(e.getMessage())); - } - try { - jsonObject.getDouble("stringKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"stringKey\"] is not a number.". - equals(e.getMessage())); - } - try { - jsonObject.getInt("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"nonKey\"] not found.". - equals(e.getMessage())); - } - try { - jsonObject.getInt("stringKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"stringKey\"] is not an int.". - equals(e.getMessage())); - } - try { - jsonObject.getLong("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"nonKey\"] not found.". - equals(e.getMessage())); - } - try { - jsonObject.getLong("stringKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"stringKey\"] is not a long.". - equals(e.getMessage())); - } - try { - jsonObject.getJSONArray("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"nonKey\"] not found.". - equals(e.getMessage())); - } - try { - jsonObject.getJSONArray("stringKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"stringKey\"] is not a JSONArray.". - equals(e.getMessage())); - } - try { - jsonObject.getJSONObject("nonKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"nonKey\"] not found.". - equals(e.getMessage())); - } - try { - jsonObject.getJSONObject("stringKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[\"stringKey\"] is not a JSONObject.". - equals(e.getMessage())); - } - } - - /** - * This test documents an unexpected numeric behavior. - * A double that ends with .0 is parsed, serialized, then - * parsed again. On the second parse, it has become an int. - */ - @Test - public void unexpectedDoubleToIntConversion() { - String key30 = "key30"; - String key31 = "key31"; - JSONObject jsonObject = new JSONObject(); - jsonObject.put(key30, new Double(3.0)); - jsonObject.put(key31, new Double(3.1)); - - assertTrue("3.0 should remain a double", - jsonObject.getDouble(key30) == 3); - assertTrue("3.1 should remain a double", - jsonObject.getDouble(key31) == 3.1); - - // turns 3.0 into 3. - String serializedString = jsonObject.toString(); - JSONObject deserialized = new JSONObject(serializedString); - assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer); - assertTrue("3.0 can still be interpreted as a double", - deserialized.getDouble(key30) == 3.0); - assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1); - } - - /** - * Document behaviors of big numbers. Includes both JSONObject - * and JSONArray tests - */ - @Test - public void bigNumberOperations() { - /** - * JSONObject tries to parse BigInteger as a bean, but it only has - * one getter, getLowestBitSet(). The value is lost and an unhelpful - * value is stored. This should be fixed. - */ - BigInteger bigInteger = new BigInteger("123456789012345678901234567890"); - JSONObject jsonObject = new JSONObject(bigInteger); - Object obj = jsonObject.get("lowestSetBit"); - assertTrue("JSONObject only has 1 value", jsonObject.length() == 1); - assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet", - obj instanceof Integer); - assertTrue("this bigInteger lowestBitSet happens to be 1", - obj.equals(1)); - - /** - * JSONObject tries to parse BigDecimal as a bean, but it has - * no getters, The value is lost and no value is stored. - * This should be fixed. - */ - BigDecimal bigDecimal = new BigDecimal( - "123456789012345678901234567890.12345678901234567890123456789"); - jsonObject = new JSONObject(bigDecimal); - assertTrue("large bigDecimal is not stored", jsonObject.length() == 0); - - /** - * JSONObject put(String, Object) method stores and serializes - * bigInt and bigDec correctly. Nothing needs to change. - */ - jsonObject = new JSONObject(); - jsonObject.put("bigInt", bigInteger); - assertTrue("jsonObject.put() handles bigInt correctly", - jsonObject.get("bigInt").equals(bigInteger)); - assertTrue("jsonObject.getBigInteger() handles bigInt correctly", - jsonObject.getBigInteger("bigInt").equals(bigInteger)); - assertTrue("jsonObject.optBigInteger() handles bigInt correctly", - jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger)); - assertTrue("jsonObject serializes bigInt correctly", - jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}")); - jsonObject = new JSONObject(); - jsonObject.put("bigDec", bigDecimal); - assertTrue("jsonObject.put() handles bigDec correctly", - jsonObject.get("bigDec").equals(bigDecimal)); - assertTrue("jsonObject.getBigDecimal() handles bigDec correctly", - jsonObject.getBigDecimal("bigDec").equals(bigDecimal)); - assertTrue("jsonObject.optBigDecimal() handles bigDec correctly", - jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal)); - assertTrue("jsonObject serializes bigDec correctly", - jsonObject.toString().equals( - "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}")); - - /** - * exercise some exceptions - */ - try { - jsonObject.getBigDecimal("bigInt"); - assertTrue("expected an exeption", false); - } catch (JSONException ignored) {} - obj = jsonObject.optBigDecimal("bigInt", BigDecimal.ONE); - assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE)); - try { - jsonObject.getBigInteger("bigDec"); - assertTrue("expected an exeption", false); - } catch (JSONException ignored) {} - jsonObject.put("stringKey", "abc"); - try { - jsonObject.getBigDecimal("stringKey"); - assertTrue("expected an exeption", false); - } catch (JSONException ignored) {} - obj = jsonObject.optBigInteger("bigDec", BigInteger.ONE); - assertTrue("expected BigInteger", obj.equals(BigInteger.ONE)); - - /** - * JSONObject.numberToString() works correctly, nothing to change. - */ - String str = JSONObject.numberToString(bigInteger); - assertTrue("numberToString() handles bigInteger correctly", - str.equals("123456789012345678901234567890")); - str = JSONObject.numberToString(bigDecimal); - assertTrue("numberToString() handles bigDecimal correctly", - str.equals("123456789012345678901234567890.12345678901234567890123456789")); - - /** - * JSONObject.stringToValue() turns bigInt into an accurate string, - * and rounds bigDec. This incorrect, but users may have come to - * expect this behavior. Change would be marginally better, but - * might inconvenience users. - */ - obj = JSONObject.stringToValue(bigInteger.toString()); - assertTrue("stringToValue() turns bigInteger string into string", - obj instanceof String); - obj = JSONObject.stringToValue(bigDecimal.toString()); - assertTrue("stringToValue() changes bigDecimal string", - !obj.toString().equals(bigDecimal.toString())); - - /** - * wrap() vs put() big number behavior is now the same. - */ - // bigInt map ctor - Map map = new HashMap(); - map.put("bigInt", bigInteger); - jsonObject = new JSONObject(map); - String actualFromMapStr = jsonObject.toString(); - assertTrue("bigInt in map (or array or bean) is a string", - actualFromMapStr.equals( - "{\"bigInt\":123456789012345678901234567890}")); - // bigInt put - jsonObject = new JSONObject(); - jsonObject.put("bigInt", bigInteger); - String actualFromPutStr = jsonObject.toString(); - assertTrue("bigInt from put is a number", - actualFromPutStr.equals( - "{\"bigInt\":123456789012345678901234567890}")); - // bigDec map ctor - map = new HashMap(); - map.put("bigDec", bigDecimal); - jsonObject = new JSONObject(map); - actualFromMapStr = jsonObject.toString(); - assertTrue("bigDec in map (or array or bean) is a bigDec", - actualFromMapStr.equals( - "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}")); - // bigDec put - jsonObject = new JSONObject(); - jsonObject.put("bigDec", bigDecimal); - actualFromPutStr = jsonObject.toString(); - assertTrue("bigDec from put is a number", - actualFromPutStr.equals( - "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}")); - // bigInt,bigDec put - JSONArray jsonArray = new JSONArray(); - jsonArray.put(bigInteger); - jsonArray.put(bigDecimal); - actualFromPutStr = jsonArray.toString(); - assertTrue("bigInt, bigDec from put is a number", - actualFromPutStr.equals( - "[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]")); - assertTrue("getBigInt is bigInt", jsonArray.getBigInteger(0).equals(bigInteger)); - assertTrue("getBigDec is bigDec", jsonArray.getBigDecimal(1).equals(bigDecimal)); - assertTrue("optBigInt is bigInt", jsonArray.optBigInteger(0, BigInteger.ONE).equals(bigInteger)); - assertTrue("optBigDec is bigDec", jsonArray.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal)); - jsonArray.put(Boolean.TRUE); - try { - jsonArray.getBigInteger(2); - assertTrue("should not be able to get big int", false); - } catch (Exception ignored) {} - try { - jsonArray.getBigDecimal(2); - assertTrue("should not be able to get big dec", false); - } catch (Exception ignored) {} - assertTrue("optBigInt is default", jsonArray.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE)); - assertTrue("optBigDec is default", jsonArray.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE)); - - // bigInt,bigDec list ctor - List list = new ArrayList(); - list.add(bigInteger); - list.add(bigDecimal); - jsonArray = new JSONArray(list); - String actualFromListStr = jsonArray.toString(); - assertTrue("bigInt, bigDec in list is a bigInt, bigDec", - actualFromListStr.equals( - "[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]")); - // bigInt bean ctor - MyBigNumberBean myBigNumberBean = mock(MyBigNumberBean.class); - when(myBigNumberBean.getBigInteger()).thenReturn(new BigInteger("123456789012345678901234567890")); - jsonObject = new JSONObject(myBigNumberBean); - String actualFromBeanStr = jsonObject.toString(); - // can't do a full string compare because mockery adds an extra key/value - assertTrue("bigInt from bean ctor is a bigInt", - actualFromBeanStr.contains("123456789012345678901234567890")); - // bigDec bean ctor - myBigNumberBean = mock(MyBigNumberBean.class); - when(myBigNumberBean.getBigDecimal()).thenReturn(new BigDecimal("123456789012345678901234567890.12345678901234567890123456789")); - jsonObject = new JSONObject(myBigNumberBean); - actualFromBeanStr = jsonObject.toString(); - // can't do a full string compare because mockery adds an extra key/value - assertTrue("bigDec from bean ctor is a bigDec", - actualFromBeanStr.contains("123456789012345678901234567890.12345678901234567890123456789")); - // bigInt,bigDec wrap() - obj = JSONObject.wrap(bigInteger); - assertTrue("wrap() returns big num",obj.equals(bigInteger)); - obj = JSONObject.wrap(bigDecimal); - assertTrue("wrap() returns string",obj.equals(bigDecimal)); - - } - - /** - * The purpose for the static method getNames() methods are not clear. - * This method is not called from within JSON-Java. Most likely - * uses are to prep names arrays for: - * JSONObject(JSONObject jo, String[] names) - * JSONObject(Object object, String names[]), - */ - @Test - public void jsonObjectNames() { - JSONObject jsonObject; - - // getNames() from null JSONObject - assertTrue("null names from null Object", - null == JSONObject.getNames((Object)null)); - - // getNames() from object with no fields - assertTrue("null names from Object with no fields", - null == JSONObject.getNames(new MyJsonString())); - - // getNames from new JSONOjbect - jsonObject = new JSONObject(); - String [] names = JSONObject.getNames(jsonObject); - assertTrue("names should be null", names == null); - - - // getNames() from empty JSONObject - String emptyStr = "{}"; - jsonObject = new JSONObject(emptyStr); - assertTrue("empty JSONObject should have null names", - null == JSONObject.getNames(jsonObject)); - - // getNames() from JSONObject - String str = - "{"+ - "\"trueKey\":true,"+ - "\"falseKey\":false,"+ - "\"stringKey\":\"hello world!\","+ - "}"; - jsonObject = new JSONObject(str); - names = JSONObject.getNames(jsonObject); - JSONArray jsonArray = new JSONArray(names); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider() - .parse(jsonArray.toString()); - List docList = JsonPath.read(doc, "$"); - assertTrue("expected 3 items", docList.size() == 3); - assertTrue( - "expected to find trueKey", - ((List) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1); - assertTrue( - "expected to find falseKey", - ((List) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1); - assertTrue( - "expected to find stringKey", - ((List) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1); - - /** - * getNames() from an enum with properties has an interesting result. - * It returns the enum values, not the selected enum properties - */ - MyEnumField myEnumField = MyEnumField.VAL1; - names = JSONObject.getNames(myEnumField); - - // validate JSON - jsonArray = new JSONArray(names); - doc = Configuration.defaultConfiguration().jsonProvider() - .parse(jsonArray.toString()); - docList = JsonPath.read(doc, "$"); - assertTrue("expected 3 items", docList.size() == 3); - assertTrue( - "expected to find VAL1", - ((List) JsonPath.read(doc, "$[?(@=='VAL1')]")).size() == 1); - assertTrue( - "expected to find VAL2", - ((List) JsonPath.read(doc, "$[?(@=='VAL2')]")).size() == 1); - assertTrue( - "expected to find VAL3", - ((List) JsonPath.read(doc, "$[?(@=='VAL3')]")).size() == 1); - - /** - * A bean is also an object. But in order to test the static - * method getNames(), this particular bean needs some public - * data members. - */ - MyPublicClass myPublicClass = new MyPublicClass(); - names = JSONObject.getNames(myPublicClass); - - // validate JSON - jsonArray = new JSONArray(names); - doc = Configuration.defaultConfiguration().jsonProvider() - .parse(jsonArray.toString()); - docList = JsonPath.read(doc, "$"); - assertTrue("expected 2 items", docList.size() == 2); - assertTrue( - "expected to find publicString", - ((List) JsonPath.read(doc, "$[?(@=='publicString')]")).size() == 1); - assertTrue( - "expected to find publicInt", - ((List) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1); - } - - /** - * Populate a JSONArray from an empty JSONObject names() method. - * It should be empty. - */ - @Test - public void emptyJsonObjectNamesToJsonAray() { - JSONObject jsonObject = new JSONObject(); - JSONArray jsonArray = jsonObject.names(); - assertTrue("jsonArray should be null", jsonArray == null); - } - - /** - * Populate a JSONArray from a JSONObject names() method. - * Confirm that it contains the expected names. - */ - @Test - public void jsonObjectNamesToJsonAray() { - String str = - "{"+ - "\"trueKey\":true,"+ - "\"falseKey\":false,"+ - "\"stringKey\":\"hello world!\","+ - "}"; - - JSONObject jsonObject = new JSONObject(str); - JSONArray jsonArray = jsonObject.names(); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString()); - assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); - assertTrue("expected to find trueKey", ((List) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1); - assertTrue("expected to find falseKey", ((List) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1); - assertTrue("expected to find stringKey", ((List) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1); - } - - /** - * Exercise the JSONObject increment() method. - */ - @Test - public void jsonObjectIncrement() { - String str = - "{"+ - "\"keyLong\":9999999991,"+ - "\"keyDouble\":1.1"+ - "}"; - JSONObject jsonObject = new JSONObject(str); - jsonObject.increment("keyInt"); - jsonObject.increment("keyInt"); - jsonObject.increment("keyLong"); - jsonObject.increment("keyDouble"); - jsonObject.increment("keyInt"); - jsonObject.increment("keyLong"); - jsonObject.increment("keyDouble"); - /** - * JSONObject constructor won't handle these types correctly, but - * adding them via put works. - */ - jsonObject.put("keyFloat", new Float(1.1)); - jsonObject.put("keyBigInt", new BigInteger("123456789123456789123456789123456780")); - jsonObject.put("keyBigDec", new BigDecimal("123456789123456789123456789123456780.1")); - jsonObject.increment("keyFloat"); - jsonObject.increment("keyFloat"); - jsonObject.increment("keyBigInt"); - jsonObject.increment("keyBigDec"); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 6 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 6); - assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/keyInt"))); - assertTrue("expected 9999999993", Long.valueOf(9999999993L).equals(jsonObject.query("/keyLong"))); - assertTrue("expected 3.1", Double.valueOf(3.1).equals(jsonObject.query("/keyDouble"))); - assertTrue("expected 123456789123456789123456789123456781", new BigInteger("123456789123456789123456789123456781").equals(jsonObject.query("/keyBigInt"))); - assertTrue("expected 123456789123456789123456789123456781.1", new BigDecimal("123456789123456789123456789123456781.1").equals(jsonObject.query("/keyBigDec"))); - - /** - * Should work the same way on any platform! @see https://docs.oracle - * .com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3 This is the - * effect of a float to double conversion and is inherent to the - * shortcomings of the IEEE 754 format, when converting 32-bit into - * double-precision 64-bit. Java type-casts float to double. A 32 bit - * float is type-casted to 64 bit double by simply appending zero-bits - * to the mantissa (and extended the signed exponent by 3 bits.) and - * there is no way to obtain more information than it is stored in the - * 32-bits float. - * - * Like 1/3 cannot be represented as base10 number because it is - * periodically, 1/5 (for example) cannot be represented as base2 number - * since it is periodically in base2 (take a look at - * http://www.h-schmidt.net/FloatConverter/) The same happens to 3.1, - * that decimal number (base10 representation) is periodic in base2 - * representation, therefore appending zero-bits is inaccurate. Only - * repeating the periodically occuring bits (0110) would be a proper - * conversion. However one cannot detect from a 32 bit IEE754 - * representation which bits would "repeat infinitely", since the - * missing bits would not fit into the 32 bit float, i.e. the - * information needed simply is not there! - */ - assertTrue("expected 3.0999999046325684", Double.valueOf(3.0999999046325684).equals(jsonObject.query("/keyFloat"))); - - /** - * float f = 3.1f; double df = (double) f; double d = 3.1d; - * System.out.println - * (Integer.toBinaryString(Float.floatToRawIntBits(f))); - * System.out.println - * (Long.toBinaryString(Double.doubleToRawLongBits(df))); - * System.out.println - * (Long.toBinaryString(Double.doubleToRawLongBits(d))); - * - * - Float: - * seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm - * 1000000010001100110011001100110 - * - Double - * seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm - * 10000000 10001100110011001100110 - * 100000000001000110011001100110011000000000000000000000000000000 - * 100000000001000110011001100110011001100110011001100110011001101 - */ - - /** - * Examples of well documented but probably unexpected behavior in - * java / with 32-bit float to 64-bit float conversion. - */ - assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double)0.2f == 0.2d ); - assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d ); - Double d1 = new Double( 1.1f ); - Double d2 = new Double( "1.1f" ); - assertFalse( "Document implicit type cast from float to double before calling Double(double d) constructor", d1.equals( d2 ) ); - - assertTrue( "Correctly converting float to double via base10 (string) representation!", new Double( 3.1d ).equals( new Double( new Float( 3.1f ).toString() ) ) ); - - // Pinpointing the not so obvious "buggy" conversion from float to double in JSONObject - JSONObject jo = new JSONObject(); - jo.put( "bug", 3.1f ); // will call put( String key, double value ) with implicit and "buggy" type-cast from float to double - assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( new Double( 3.1d ) ) ); - - JSONObject inc = new JSONObject(); - inc.put( "bug", new Float( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value ) - assertTrue( "Everything is ok here!", inc.get( "bug" ) instanceof Float ); - inc.increment( "bug" ); // after adding 1, increment will call put( String key, double value ) with implicit and "buggy" type-cast from float to double! - // this.put(key, (Float) value + 1); - // 1. The (Object)value will be typecasted to (Float)value since it is an instanceof Float actually nothing is done. - // 2. Float instance will be autoboxed into float because the + operator will work on primitives not Objects! - // 3. A float+float operation will be performed and results into a float primitive. - // 4. There is no method that matches the signature put( String key, float value), java-compiler will choose the method - // put( String key, double value) and does an implicit type-cast(!) by appending zero-bits to the mantissa - assertTrue( "JSONObject increment converts Float to Double", jo.get( "bug" ) instanceof Double ); - // correct implementation (with change of behavior) would be: - // this.put(key, new Float((Float) value + 1)); - // Probably it would be better to deprecate the method and remove some day, while convenient processing the "payload" is not - // really in the the scope of a JSON-library (IMHO.) - - } - - /** - * Exercise JSONObject numberToString() method - */ - @Test - public void jsonObjectNumberToString() { - String str; - Double dVal; - Integer iVal = 1; - str = JSONObject.numberToString(iVal); - assertTrue("expected "+iVal+" actual "+str, iVal.toString().equals(str)); - dVal = 12.34; - str = JSONObject.numberToString(dVal); - assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str)); - dVal = 12.34e27; - str = JSONObject.numberToString(dVal); - assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str)); - // trailing .0 is truncated, so it doesn't quite match toString() - dVal = 5000000.0000000; - str = JSONObject.numberToString(dVal); - assertTrue("expected 5000000 actual "+str, str.equals("5000000")); - } - - /** - * Exercise JSONObject put() and similar() methods - */ - @Test - public void jsonObjectPut() { - String expectedStr = - "{"+ - "\"trueKey\":true,"+ - "\"falseKey\":false,"+ - "\"arrayKey\":[0,1,2],"+ - "\"objectKey\":{"+ - "\"myKey1\":\"myVal1\","+ - "\"myKey2\":\"myVal2\","+ - "\"myKey3\":\"myVal3\","+ - "\"myKey4\":\"myVal4\""+ - "}"+ - "}"; - JSONObject jsonObject = new JSONObject(); - jsonObject.put("trueKey", true); - jsonObject.put("falseKey", false); - Integer [] intArray = { 0, 1, 2 }; - jsonObject.put("arrayKey", Arrays.asList(intArray)); - Map myMap = new HashMap(); - myMap.put("myKey1", "myVal1"); - myMap.put("myKey2", "myVal2"); - myMap.put("myKey3", "myVal3"); - myMap.put("myKey4", "myVal4"); - jsonObject.put("objectKey", myMap); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 4 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 4); - assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); - assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); - assertTrue("expected 3 arrayKey items", ((List)(JsonPath.read(doc, "$.arrayKey"))).size() == 3); - assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0"))); - assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1"))); - assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2"))); - assertTrue("expected 4 objectKey items", ((Map)(JsonPath.read(doc, "$.objectKey"))).size() == 4); - assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1"))); - assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2"))); - assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3"))); - assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4"))); - - jsonObject.remove("trueKey"); - JSONObject expectedJsonObject = new JSONObject(expectedStr); - assertTrue("unequal jsonObjects should not be similar", - !jsonObject.similar(expectedJsonObject)); - assertTrue("jsonObject should not be similar to jsonArray", - !jsonObject.similar(new JSONArray())); - - String aCompareValueStr = "{\"a\":\"aval\",\"b\":true}"; - String bCompareValueStr = "{\"a\":\"notAval\",\"b\":true}"; - JSONObject aCompareValueJsonObject = new JSONObject(aCompareValueStr); - JSONObject bCompareValueJsonObject = new JSONObject(bCompareValueStr); - assertTrue("different values should not be similar", - !aCompareValueJsonObject.similar(bCompareValueJsonObject)); - - String aCompareObjectStr = "{\"a\":\"aval\",\"b\":{}}"; - String bCompareObjectStr = "{\"a\":\"aval\",\"b\":true}"; - JSONObject aCompareObjectJsonObject = new JSONObject(aCompareObjectStr); - JSONObject bCompareObjectJsonObject = new JSONObject(bCompareObjectStr); - assertTrue("different nested JSONObjects should not be similar", - !aCompareObjectJsonObject.similar(bCompareObjectJsonObject)); - - String aCompareArrayStr = "{\"a\":\"aval\",\"b\":[]}"; - String bCompareArrayStr = "{\"a\":\"aval\",\"b\":true}"; - JSONObject aCompareArrayJsonObject = new JSONObject(aCompareArrayStr); - JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr); - assertTrue("different nested JSONArrays should not be similar", - !aCompareArrayJsonObject.similar(bCompareArrayJsonObject)); - } - - /** - * Exercise JSONObject toString() method - */ - @Test - public void jsonObjectToString() { - String str = - "{"+ - "\"trueKey\":true,"+ - "\"falseKey\":false,"+ - "\"arrayKey\":[0,1,2],"+ - "\"objectKey\":{"+ - "\"myKey1\":\"myVal1\","+ - "\"myKey2\":\"myVal2\","+ - "\"myKey3\":\"myVal3\","+ - "\"myKey4\":\"myVal4\""+ - "}"+ - "}"; - JSONObject jsonObject = new JSONObject(str); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 4 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 4); - assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); - assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); - assertTrue("expected 3 arrayKey items", ((List)(JsonPath.read(doc, "$.arrayKey"))).size() == 3); - assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0"))); - assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1"))); - assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2"))); - assertTrue("expected 4 objectKey items", ((Map)(JsonPath.read(doc, "$.objectKey"))).size() == 4); - assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1"))); - assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2"))); - assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3"))); - assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4"))); - } - - /** - * Explores how JSONObject handles maps. Insert a string/string map - * as a value in a JSONObject. It will remain a map. Convert the - * JSONObject to string, then create a new JSONObject from the string. - * In the new JSONObject, the value will be stored as a nested JSONObject. - * Confirm that map and nested JSONObject have the same contents. - */ - @Test - public void jsonObjectToStringSuppressWarningOnCastToMap() { - JSONObject jsonObject = new JSONObject(); - Map map = new HashMap<>(); - map.put("abc", "def"); - jsonObject.put("key", map); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); - assertTrue("expected 1 key item", ((Map)(JsonPath.read(doc, "$.key"))).size() == 1); - assertTrue("expected def", "def".equals(jsonObject.query("/key/abc"))); - } - - /** - * Explores how JSONObject handles collections. Insert a string collection - * as a value in a JSONObject. It will remain a collection. Convert the - * JSONObject to string, then create a new JSONObject from the string. - * In the new JSONObject, the value will be stored as a nested JSONArray. - * Confirm that collection and nested JSONArray have the same contents. - */ - @Test - public void jsonObjectToStringSuppressWarningOnCastToCollection() { - JSONObject jsonObject = new JSONObject(); - Collection collection = new ArrayList(); - collection.add("abc"); - // ArrayList will be added as an object - jsonObject.put("key", collection); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); - assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); - assertTrue("expected 1 key item", ((List)(JsonPath.read(doc, "$.key"))).size() == 1); - assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0"))); - } - - /** - * Exercises the JSONObject.valueToString() method for various types - */ - @Test - public void valueToString() { - - assertTrue("null valueToString() incorrect", - "null".equals(JSONObject.valueToString(null))); - MyJsonString jsonString = new MyJsonString(); - assertTrue("jsonstring valueToString() incorrect", - "my string".equals(JSONObject.valueToString(jsonString))); - assertTrue("boolean valueToString() incorrect", - "true".equals(JSONObject.valueToString(Boolean.TRUE))); - assertTrue("non-numeric double", - "null".equals(JSONObject.doubleToString(Double.POSITIVE_INFINITY))); - String jsonObjectStr = - "{"+ - "\"key1\":\"val1\","+ - "\"key2\":\"val2\","+ - "\"key3\":\"val3\""+ - "}"; - JSONObject jsonObject = new JSONObject(jsonObjectStr); - assertTrue("jsonObject valueToString() incorrect", - JSONObject.valueToString(jsonObject).equals(jsonObject.toString())); - String jsonArrayStr = - "[1,2,3]"; - JSONArray jsonArray = new JSONArray(jsonArrayStr); - assertTrue("jsonArra valueToString() incorrect", - JSONObject.valueToString(jsonArray).equals(jsonArray.toString())); - Map map = new HashMap(); - map.put("key1", "val1"); - map.put("key2", "val2"); - map.put("key3", "val3"); - assertTrue("map valueToString() incorrect", - jsonObject.toString().equals(JSONObject.valueToString(map))); - Collection collection = new ArrayList(); - collection.add(new Integer(1)); - collection.add(new Integer(2)); - collection.add(new Integer(3)); - assertTrue("collection valueToString() expected: "+ - jsonArray.toString()+ " actual: "+ - JSONObject.valueToString(collection), - jsonArray.toString().equals(JSONObject.valueToString(collection))); - Integer[] array = { new Integer(1), new Integer(2), new Integer(3) }; - assertTrue("array valueToString() incorrect", - jsonArray.toString().equals(JSONObject.valueToString(array))); - } - - /** - * Confirm that https://github.com/douglascrockford/JSON-java/issues/167 is fixed. - * The following code was throwing a ClassCastException in the - * JSONObject(Map) constructor - */ - @Test - public void valueToStringConfirmException() { - Map myMap = new HashMap(); - myMap.put(1, "myValue"); - // this is the test, it should not throw an exception - String str = JSONObject.valueToString(myMap); - // confirm result, just in case - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(str); - assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); - assertTrue("expected myValue", "myValue".equals(JsonPath.read(doc, "$.1"))); - } - - /** - * Exercise the JSONObject wrap() method. Sometimes wrap() will change - * the object being wrapped, other times not. The purpose of wrap() is - * to ensure the value is packaged in a way that is compatible with how - * a JSONObject value or JSONArray value is supposed to be stored. - */ - @Test - public void wrapObject() { - // wrap(null) returns NULL - assertTrue("null wrap() incorrect", - JSONObject.NULL == JSONObject.wrap(null)); - - // wrap(Integer) returns Integer - Integer in = new Integer(1); - assertTrue("Integer wrap() incorrect", - in == JSONObject.wrap(in)); - - /** - * This test is to document the preferred behavior if BigDecimal is - * supported. Previously bd returned as a string, since it - * is recognized as being a Java package class. Now with explicit - * support for big numbers, it remains a BigDecimal - */ - Object bdWrap = JSONObject.wrap(BigDecimal.ONE); - assertTrue("BigDecimal.ONE evaluates to ONE", - bdWrap.equals(BigDecimal.ONE)); - - // wrap JSONObject returns JSONObject - String jsonObjectStr = - "{"+ - "\"key1\":\"val1\","+ - "\"key2\":\"val2\","+ - "\"key3\":\"val3\""+ - "}"; - JSONObject jsonObject = new JSONObject(jsonObjectStr); - assertTrue("JSONObject wrap() incorrect", - jsonObject == JSONObject.wrap(jsonObject)); - - // wrap collection returns JSONArray - Collection collection = new ArrayList(); - collection.add(new Integer(1)); - collection.add(new Integer(2)); - collection.add(new Integer(3)); - JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection)); - - // validate JSON - Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString()); - assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); - assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0"))); - assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1"))); - assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2"))); - - // wrap Array returns JSONArray - Integer[] array = { new Integer(1), new Integer(2), new Integer(3) }; - JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array)); - - // validate JSON - doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString()); - assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); - assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0"))); - assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1"))); - assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2"))); - - // validate JSON - doc = Configuration.defaultConfiguration().jsonProvider().parse(integerArrayJsonArray.toString()); - assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); - assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0"))); - assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1"))); - assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2"))); - - // wrap map returns JSONObject - Map map = new HashMap(); - map.put("key1", "val1"); - map.put("key2", "val2"); - map.put("key3", "val3"); - JSONObject mapJsonObject = (JSONObject) (JSONObject.wrap(map)); - - // validate JSON - doc = Configuration.defaultConfiguration().jsonProvider().parse(mapJsonObject.toString()); - assertTrue("expected 3 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 3); - assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1"))); - assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2"))); - assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3"))); - } - - /** - * Explore how JSONObject handles parsing errors. - */ - @Test - public void jsonObjectParsingErrors() { - try { - // does not start with '{' - String str = "abc"; - new JSONObject(str); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "A JSONObject text must begin with '{' at 1 [character 2 line 1]". - equals(e.getMessage())); - } - try { - // does not end with '}' - String str = "{"; - new JSONObject(str); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "A JSONObject text must end with '}' at 2 [character 3 line 1]". - equals(e.getMessage())); - } - try { - // key with no ':' - String str = "{\"myKey\" = true}"; - new JSONObject(str); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Expected a ':' after a key at 10 [character 11 line 1]". - equals(e.getMessage())); - } - try { - // entries with no ',' separator - String str = "{\"myKey\":true \"myOtherKey\":false}"; - new JSONObject(str); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Expected a ',' or '}' at 15 [character 16 line 1]". - equals(e.getMessage())); - } - try { - // append to wrong key - String str = "{\"myKey\":true, \"myOtherKey\":false}"; - JSONObject jsonObject = new JSONObject(str); - jsonObject.append("myKey", "hello"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "JSONObject[myKey] is not a JSONArray.". - equals(e.getMessage())); - } - try { - // increment wrong key - String str = "{\"myKey\":true, \"myOtherKey\":false}"; - JSONObject jsonObject = new JSONObject(str); - jsonObject.increment("myKey"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Unable to increment [\"myKey\"].". - equals(e.getMessage())); - } - try { - // invalid key - String str = "{\"myKey\":true, \"myOtherKey\":false}"; - JSONObject jsonObject = new JSONObject(str); - jsonObject.get(null); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Null key.". - equals(e.getMessage())); - } - try { - // invalid numberToString() - JSONObject.numberToString((Number)null); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Null pointer". - equals(e.getMessage())); - } - try { - // null put key - JSONObject jsonObject = new JSONObject("{}"); - jsonObject.put(null, 0); - assertTrue("Expected an exception", false); - } catch (NullPointerException ignored) { - } - try { - // multiple putOnce key - JSONObject jsonObject = new JSONObject("{}"); - jsonObject.putOnce("hello", "world"); - jsonObject.putOnce("hello", "world!"); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("", true); - } - try { - // test validity of invalid double - JSONObject.testValidity(Double.NaN); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("", true); - } - try { - // test validity of invalid float - JSONObject.testValidity(Float.NEGATIVE_INFINITY); - assertTrue("Expected an exception", false); - } catch (JSONException e) { - assertTrue("", true); - } - } - - /** - * Confirm behavior when putOnce() is called with null parameters - */ - @Test - public void jsonObjectPutOnceNull() { - JSONObject jsonObject = new JSONObject(); - jsonObject.putOnce(null, null); - assertTrue("jsonObject should be empty", jsonObject.length() == 0); - } - - /** - * Exercise JSONObject opt(key, default) method - */ - @Test - public void jsonObjectOptDefault() { - - String str = "{\"myKey\": \"myval\"}"; - JSONObject jsonObject = new JSONObject(str); - - assertTrue("optBoolean() should return default boolean", - Boolean.TRUE == jsonObject.optBoolean("myKey", Boolean.TRUE)); - assertTrue("optInt() should return default int", - 42 == jsonObject.optInt("myKey", 42)); - assertTrue("optInt() should return default int", - 42 == jsonObject.optInt("myKey", 42)); - assertTrue("optLong() should return default long", - 42 == jsonObject.optLong("myKey", 42)); - assertTrue("optDouble() should return default double", - 42.3 == jsonObject.optDouble("myKey", 42.3)); - assertTrue("optString() should return default string", - "hi".equals(jsonObject.optString("hiKey", "hi"))); - } - - /** - * Confirm behavior when JSONObject put(key, null object) is called - */ - @Test - public void jsonObjectputNull() { - - // put null should remove the item. - String str = "{\"myKey\": \"myval\"}"; - JSONObject jsonObjectRemove = new JSONObject(str); - jsonObjectRemove.remove("myKey"); - - JSONObject jsonObjectPutNull = new JSONObject(str); - jsonObjectPutNull.put("myKey", (Object) null); - - // validate JSON - assertTrue("jsonObject should be empty", jsonObjectRemove.length() == 0 - && jsonObjectPutNull.length() == 0); - } - - /** - * Exercise JSONObject quote() method - * This purpose of quote() is to ensure that for strings with embedded - * quotes, the quotes are properly escaped. - */ - @Test - public void jsonObjectQuote() { - String str; - str = ""; - String quotedStr; - quotedStr = JSONObject.quote(str); - assertTrue("quote() expected escaped quotes, found "+quotedStr, - "\"\"".equals(quotedStr)); - str = "\"\""; - quotedStr = JSONObject.quote(str); - assertTrue("quote() expected escaped quotes, found "+quotedStr, - "\"\\\"\\\"\"".equals(quotedStr)); - str = "null and null will be emitted as "" - */ - String sJONull = XML.toString(jsonObjectJONull); - assertTrue("JSONObject.NULL should emit a null value", - "null".equals(sJONull)); - String sNull = XML.toString(jsonObjectNull); - assertTrue("null should emit an empty string", "".equals(sNull)); - } - - @Test(expected = JSONPointerException.class) - public void queryWithNoResult() { - new JSONObject().query("/a/b"); - } - - @Test - public void optQueryWithNoResult() { - assertNull(new JSONObject().optQuery("/a/b")); - } - - @Test(expected = IllegalArgumentException.class) - public void optQueryWithSyntaxError() { - new JSONObject().optQuery("invalid"); - } - - @Test(expected = JSONException.class) - public void invalidEscapeSequence() { - String json = "{ \"\\url\": \"value\" }"; - new JSONObject(json); - } -} +package org.json.junit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +import org.json.CDL; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONPointerException; +import org.json.XML; +import org.json.junit.data.BrokenToString; +import org.json.junit.data.ExceptionalBean; +import org.json.junit.data.Fraction; +import org.json.junit.data.GenericBean; +import org.json.junit.data.GenericBeanInt; +import org.json.junit.data.MyBean; +import org.json.junit.data.MyBeanCustomName; +import org.json.junit.data.MyBeanCustomNameSubClass; +import org.json.junit.data.MyBigNumberBean; +import org.json.junit.data.MyEnum; +import org.json.junit.data.MyEnumField; +import org.json.junit.data.MyJsonString; +import org.json.junit.data.MyNumber; +import org.json.junit.data.MyNumberContainer; +import org.json.junit.data.MyPublicClass; +import org.json.junit.data.Singleton; +import org.json.junit.data.SingletonEnum; +import org.json.junit.data.WeirdList; +import org.junit.Test; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.JsonPath; + +/** + * JSONObject, along with JSONArray, are the central classes of the reference app. + * All of the other classes interact with them, and JSON functionality would + * otherwise be impossible. + */ +public class JSONObjectTest { + + /** + * Regular Expression Pattern that matches JSON Numbers. This is primarily used for + * output to guarantee that we are always writing valid JSON. + */ + static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?"); + + /** + * Tests that the similar method is working as expected. + */ + @Test + public void verifySimilar() { + final String string1 = "HasSameRef"; + JSONObject obj1 = new JSONObject() + .put("key1", "abc") + .put("key2", 2) + .put("key3", string1); + + JSONObject obj2 = new JSONObject() + .put("key1", "abc") + .put("key2", 3) + .put("key3", string1); + + JSONObject obj3 = new JSONObject() + .put("key1", "abc") + .put("key2", 2) + .put("key3", new String(string1)); + + assertFalse("Should eval to false", obj1.similar(obj2)); + + assertTrue("Should eval to true", obj1.similar(obj3)); + + } + + @Test + public void timeNumberParsing() { + // test data to use + final String[] testData = new String[] { + null, + "", + "100", + "-100", + "abc123", + "012345", + "100.5e199", + "-100.5e199", + "DEADBEEF", + "0xDEADBEEF", + "1234567890.1234567890", + "-1234567890.1234567890", + "adloghakuidghauiehgauioehgdkjfb nsruoh aeu noerty384 nkljfgh " + + "395h tdfn kdz8yt3 4hkls gn.ey85 4hzfhnz.o8y5a84 onvklt " + + "yh389thub nkz8y49lihv al4itlaithknty8hnbl" + // long (in length) number sequences with invalid data at the end of the + // string offer very poor performance for the REGEX. + ,"123467890123467890123467890123467890123467890123467890123467" + + "8901234678901234678901234678901234678901234678901234678" + + "9012346789012346789012346789012346789012346789012346789" + + "0a" + }; + final int testDataLength = testData.length; + /** + * Changed to 1000 for faster test runs + */ + // final int iterations = 1000000; + final int iterations = 1000; + + // 10 million iterations 1,000,000 * 10 (currently 100,000) + long startTime = System.nanoTime(); + for(int i = 0; i < iterations; i++) { + for(int j = 0; j < testDataLength; j++) { + try { + BigDecimal v1 = new BigDecimal(testData[j]); + v1.signum(); + } catch(Exception ignore) { + //do nothing + } + } + } + final long elapsedNano1 = System.nanoTime() - startTime; + System.out.println("new BigDecimal(testData[]) : " + elapsedNano1 / 1000000 + " ms"); + + startTime = System.nanoTime(); + for(int i = 0; i < iterations; i++) { + for(int j = 0; j < testDataLength; j++) { + try { + boolean v2 = NUMBER_PATTERN.matcher(testData[j]).matches(); + assert v2 == !!v2; + } catch(Exception ignore) { + //do nothing + } + } + } + final long elapsedNano2 = System.nanoTime() - startTime; + System.out.println("NUMBER_PATTERN.matcher(testData[]).matches() : " + elapsedNano2 / 1000000 + " ms"); + // don't assert normally as the testing is machine dependent. + // assertTrue("Expected Pattern matching to be faster than BigDecimal constructor",elapsedNano2)(JsonPath.read(doc, "$"))).size() == 4); + assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObjectByName.query("/falseKey"))); + assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey"))); + assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObjectByName.query("/stringKey"))); + assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObjectByName.query("/doubleKey"))); + } + + /** + * JSONObjects can be built from a Map. + * In this test the map is null. + * the JSONObject(JsonTokener) ctor is not tested directly since it already + * has full coverage from other tests. + */ + @Test + public void jsonObjectByNullMap() { + Map map = null; + JSONObject jsonObject = new JSONObject(map); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); + } + + /** + * JSONObjects can be built from a Map. + * In this test all of the map entries are valid JSON types. + */ + @Test + public void jsonObjectByMap() { + Map map = new HashMap(); + map.put("trueKey", new Boolean(true)); + map.put("falseKey", new Boolean(false)); + map.put("stringKey", "hello world!"); + map.put("escapeStringKey", "h\be\tllo w\u1234orld!"); + map.put("intKey", new Long(42)); + map.put("doubleKey", new Double(-23.45e67)); + JSONObject jsonObject = new JSONObject(map); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 6 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 6); + assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); + assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); + assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey"))); + assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey"))); + assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey"))); + } + + /** + * Verifies that the constructor has backwards compatability with RAW types pre-java5. + */ + @Test + public void verifyConstructor() { + + final JSONObject expected = new JSONObject("{\"myKey\":10}"); + + @SuppressWarnings("rawtypes") + Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10)); + JSONObject jaRaw = new JSONObject(myRawC); + + Map myCStrObj = Collections.singletonMap("myKey", + (Object) Integer.valueOf(10)); + JSONObject jaStrObj = new JSONObject(myCStrObj); + + Map myCStrInt = Collections.singletonMap("myKey", + Integer.valueOf(10)); + JSONObject jaStrInt = new JSONObject(myCStrInt); + + Map myCObjObj = Collections.singletonMap((Object) "myKey", + (Object) Integer.valueOf(10)); + JSONObject jaObjObj = new JSONObject(myCObjObj); + + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaRaw)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaStrObj)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaStrInt)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaObjObj)); + } + + /** + * Tests Number serialization. + */ + @Test + public void verifyNumberOutput(){ + /** + * MyNumberContainer is a POJO, so call JSONObject(bean), + * which builds a map of getter names/values + * The only getter is getMyNumber (key=myNumber), + * whose return value is MyNumber. MyNumber extends Number, + * but is not recognized as such by wrap() per current + * implementation, so wrap() returns the default new JSONObject(bean). + * The only getter is getNumber (key=number), whose return value is + * BigDecimal(42). + */ + JSONObject jsonObject = new JSONObject(new MyNumberContainer()); + String actual = jsonObject.toString(); + String expected = "{\"myNumber\":{\"number\":42}}"; + assertEquals("Equal", expected , actual); + + /** + * JSONObject.put() handles objects differently than the + * bean constructor. Where the bean ctor wraps objects before + * placing them in the map, put() inserts the object without wrapping. + * In this case, a MyNumber instance is the value. + * The MyNumber.toString() method is responsible for + * returning a reasonable value: the string '42'. + */ + jsonObject = new JSONObject(); + jsonObject.put("myNumber", new MyNumber()); + actual = jsonObject.toString(); + expected = "{\"myNumber\":42}"; + assertEquals("Equal", expected , actual); + + /** + * Calls the JSONObject(Map) ctor, which calls wrap() for values. + * AtomicInteger is a Number, but is not recognized by wrap(), per + * current implementation. However, the type is + * 'java.util.concurrent.atomic', so due to the 'java' prefix, + * wrap() inserts the value as a string. That is why 42 comes back + * wrapped in quotes. + */ + jsonObject = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42))); + actual = jsonObject.toString(); + expected = "{\"myNumber\":\"42\"}"; + assertEquals("Equal", expected , actual); + + /** + * JSONObject.put() inserts the AtomicInteger directly into the + * map not calling wrap(). In toString()->write()->writeValue(), + * AtomicInteger is recognized as a Number, and converted via + * numberToString() into the unquoted string '42'. + */ + jsonObject = new JSONObject(); + jsonObject.put("myNumber", new AtomicInteger(42)); + actual = jsonObject.toString(); + expected = "{\"myNumber\":42}"; + assertEquals("Equal", expected , actual); + + /** + * Calls the JSONObject(Map) ctor, which calls wrap() for values. + * Fraction is a Number, but is not recognized by wrap(), per + * current implementation. As a POJO, Fraction is handled as a + * bean and inserted into a contained JSONObject. It has 2 getters, + * for numerator and denominator. + */ + jsonObject = new JSONObject(Collections.singletonMap("myNumber", new Fraction(4,2))); + assertEquals(1, jsonObject.length()); + assertEquals(2, ((JSONObject)(jsonObject.get("myNumber"))).length()); + assertEquals("Numerator", BigInteger.valueOf(4) , jsonObject.query("/myNumber/numerator")); + assertEquals("Denominator", BigInteger.valueOf(2) , jsonObject.query("/myNumber/denominator")); + + /** + * JSONObject.put() inserts the Fraction directly into the + * map not calling wrap(). In toString()->write()->writeValue(), + * Fraction is recognized as a Number, and converted via + * numberToString() into the unquoted string '4/2'. But the + * BigDecimal sanity check fails, so writeValue() defaults + * to returning a safe JSON quoted string. Pretty slick! + */ + jsonObject = new JSONObject(); + jsonObject.put("myNumber", new Fraction(4,2)); + actual = jsonObject.toString(); + expected = "{\"myNumber\":\"4/2\"}"; // valid JSON, bug fixed + assertEquals("Equal", expected , actual); + } + + /** + * Verifies that the put Collection has backwards compatibility with RAW types pre-java5. + */ + @Test + public void verifyPutCollection() { + + final JSONObject expected = new JSONObject("{\"myCollection\":[10]}"); + + @SuppressWarnings("rawtypes") + Collection myRawC = Collections.singleton(Integer.valueOf(10)); + JSONObject jaRaw = new JSONObject(); + jaRaw.put("myCollection", myRawC); + + Collection myCObj = Collections.singleton((Object) Integer + .valueOf(10)); + JSONObject jaObj = new JSONObject(); + jaObj.put("myCollection", myCObj); + + Collection myCInt = Collections.singleton(Integer + .valueOf(10)); + JSONObject jaInt = new JSONObject(); + jaInt.put("myCollection", myCInt); + + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaRaw)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaObj)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaInt)); + } + + + /** + * Verifies that the put Map has backwards compatibility with RAW types pre-java5. + */ + @Test + public void verifyPutMap() { + + final JSONObject expected = new JSONObject("{\"myMap\":{\"myKey\":10}}"); + + @SuppressWarnings("rawtypes") + Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10)); + JSONObject jaRaw = new JSONObject(); + jaRaw.put("myMap", myRawC); + + Map myCStrObj = Collections.singletonMap("myKey", + (Object) Integer.valueOf(10)); + JSONObject jaStrObj = new JSONObject(); + jaStrObj.put("myMap", myCStrObj); + + Map myCStrInt = Collections.singletonMap("myKey", + Integer.valueOf(10)); + JSONObject jaStrInt = new JSONObject(); + jaStrInt.put("myMap", myCStrInt); + + Map myCObjObj = Collections.singletonMap((Object) "myKey", + (Object) Integer.valueOf(10)); + JSONObject jaObjObj = new JSONObject(); + jaObjObj.put("myMap", myCObjObj); + + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaRaw)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaStrObj)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaStrInt)); + assertTrue( + "The RAW Collection should give me the same as the Typed Collection", + expected.similar(jaObjObj)); + } + + + /** + * JSONObjects can be built from a Map. + * In this test the map entries are not valid JSON types. + * The actual conversion is kind of interesting. + */ + @Test + public void jsonObjectByMapWithUnsupportedValues() { + Map jsonMap = new HashMap(); + // Just insert some random objects + jsonMap.put("key1", new CDL()); + jsonMap.put("key2", new Exception()); + + JSONObject jsonObject = new JSONObject(jsonMap); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); + assertTrue("expected 0 key1 items", ((Map)(JsonPath.read(doc, "$.key1"))).size() == 0); + assertTrue("expected \"key2\":java.lang.Exception","java.lang.Exception".equals(jsonObject.query("/key2"))); + } + + /** + * JSONObjects can be built from a Map. + * In this test one of the map values is null + */ + @Test + public void jsonObjectByMapWithNullValue() { + Map map = new HashMap(); + map.put("trueKey", new Boolean(true)); + map.put("falseKey", new Boolean(false)); + map.put("stringKey", "hello world!"); + map.put("nullKey", null); + map.put("escapeStringKey", "h\be\tllo w\u1234orld!"); + map.put("intKey", new Long(42)); + map.put("doubleKey", new Double(-23.45e67)); + JSONObject jsonObject = new JSONObject(map); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 6 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 6); + assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); + assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); + assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey"))); + assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey"))); + assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey"))); + assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey"))); + } + + /** + * JSONObject built from a bean. In this case all but one of the + * bean getters return valid JSON types + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectByBean1() { + /** + * Default access classes have to be mocked since JSONObject, which is + * not in the same package, cannot call MyBean methods by reflection. + */ + MyBean myBean = mock(MyBean.class); + when(myBean.getDoubleKey()).thenReturn(-23.45e7); + when(myBean.getIntKey()).thenReturn(42); + when(myBean.getStringKey()).thenReturn("hello world!"); + when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!"); + when(myBean.isTrueKey()).thenReturn(true); + when(myBean.isFalseKey()).thenReturn(false); + when(myBean.getStringReaderKey()).thenReturn( + new StringReader("") { + }); + + JSONObject jsonObject = new JSONObject(myBean); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 8 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 8); + assertTrue("expected 0 items in stringReaderKey", ((Map) (JsonPath.read(doc, "$.stringReaderKey"))).size() == 0); + assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); + assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); + assertTrue("expected hello world!","hello world!".equals(jsonObject.query("/stringKey"))); + assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey"))); + assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey"))); + assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey"))); + // sorry, mockito artifact + assertTrue("expected 2 callbacks items", ((List)(JsonPath.read(doc, "$.callbacks"))).size() == 2); + assertTrue("expected 0 handler items", ((Map)(JsonPath.read(doc, "$.callbacks[0].handler"))).size() == 0); + assertTrue("expected 0 callbacks[1] items", ((Map)(JsonPath.read(doc, "$.callbacks[1]"))).size() == 0); + } + + /** + * JSONObject built from a bean that has custom field names. + */ + @Test + public void jsonObjectByBean2() { + JSONObject jsonObject = new JSONObject(new MyBeanCustomName()); + assertNotNull(jsonObject); + assertEquals("Wrong number of keys found:", + 5, + jsonObject.keySet().size()); + assertFalse("Normal field name (someString) processing did not work", + jsonObject.has("someString")); + assertFalse("Normal field name (myDouble) processing did not work", + jsonObject.has("myDouble")); + assertFalse("Normal field name (someFloat) processing did not work", + jsonObject.has("someFloat")); + assertFalse("Ignored field not found!", + jsonObject.has("ignoredInt")); + // getSomeInt() has no user-defined annotation + assertTrue("Normal field name (someInt) should have been found", + jsonObject.has("someInt")); + // the user-defined annotation does not replace any value, so someLong should be found + assertTrue("Normal field name (someLong) should have been found", + jsonObject.has("someLong")); + // myStringField replaces someString property name via user-defined annotation + assertTrue("Overridden String field name (myStringField) should have been found", + jsonObject.has("myStringField")); + // weird name replaces myDouble property name via user-defined annotation + assertTrue("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should have been found", + jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!")); + // InterfaceField replaces someFloat property name via user-defined annotation + assertTrue("Overridden String field name (InterfaceField) should have been found", + jsonObject.has("InterfaceField")); + } + + /** + * JSONObject built from a bean that has custom field names inherited from a parent class. + */ + @Test + public void jsonObjectByBean3() { + JSONObject jsonObject = new JSONObject(new MyBeanCustomNameSubClass()); + assertNotNull(jsonObject); + assertEquals("Wrong number of keys found:", + 7, + jsonObject.keySet().size()); + assertFalse("Normal int field name (someInt) found, but was overridden", + jsonObject.has("someInt")); + assertFalse("Normal field name (myDouble) processing did not work", + jsonObject.has("myDouble")); + // myDouble was replaced by weird name, and then replaced again by AMoreNormalName via user-defined annotation + assertFalse("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should not be FOUND!", + jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!")); + assertFalse("Normal field name (someFloat) found, but was overridden", + jsonObject.has("someFloat")); + assertFalse("Ignored field found! but was overridden", + jsonObject.has("ignoredInt")); + // shouldNotBeJSON property name was first ignored, then replaced by ShouldBeIgnored via user-defined annotations + assertFalse("Ignored field at the same level as forced name should not have been found", + jsonObject.has("ShouldBeIgnored")); + // able property name was replaced by Getable via user-defined annotation + assertFalse("Normally ignored field (able) with explicit property name should not have been found", + jsonObject.has("able")); + // property name someInt was replaced by newIntFieldName via user-defined annotation + assertTrue("Overridden int field name (newIntFieldName) should have been found", + jsonObject.has("newIntFieldName")); + // property name someLong was not replaced via user-defined annotation + assertTrue("Normal field name (someLong) should have been found", + jsonObject.has("someLong")); + // property name someString was replaced by myStringField via user-defined annotation + assertTrue("Overridden String field name (myStringField) should have been found", + jsonObject.has("myStringField")); + // property name myDouble was replaced by a weird name, followed by AMoreNormalName via user-defined annotations + assertTrue("Overridden double field name (AMoreNormalName) should have been found", + jsonObject.has("AMoreNormalName")); + // property name someFloat was replaced by InterfaceField via user-defined annotation + assertTrue("Overridden String field name (InterfaceField) should have been found", + jsonObject.has("InterfaceField")); + // property name ignoredInt was replaced by none, followed by forcedInt via user-defined annotations + assertTrue("Forced field should have been found!", + jsonObject.has("forcedInt")); + // property name able was replaced by Getable via user-defined annotation + assertTrue("Overridden boolean field name (Getable) should have been found", + jsonObject.has("Getable")); + } + + /** + * A bean is also an object. But in order to test the JSONObject + * ctor that takes an object and a list of names, + * this particular bean needs some public + * data members, which have been added to the class. + */ + @Test + public void jsonObjectByObjectAndNames() { + String[] keys = {"publicString", "publicInt"}; + // just need a class that has public data members + MyPublicClass myPublicClass = new MyPublicClass(); + JSONObject jsonObject = new JSONObject(myPublicClass, keys); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); + assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString"))); + assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt"))); + } + + /** + * Exercise the JSONObject from resource bundle functionality. + * The test resource bundle is uncomplicated, but provides adequate test coverage. + */ + @Test + public void jsonObjectByResourceBundle() { + JSONObject jsonObject = new + JSONObject("org.json.junit.data.StringsResourceBundle", + Locale.getDefault()); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 2 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 2); + assertTrue("expected 2 greetings items", ((Map)(JsonPath.read(doc, "$.greetings"))).size() == 2); + assertTrue("expected \"hello\":\"Hello, \"", "Hello, ".equals(jsonObject.query("/greetings/hello"))); + assertTrue("expected \"world\":\"World!\"", "World!".equals(jsonObject.query("/greetings/world"))); + assertTrue("expected 2 farewells items", ((Map)(JsonPath.read(doc, "$.farewells"))).size() == 2); + assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later"))); + assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator"))); + } + + /** + * Exercise the JSONObject.accumulate() method + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectAccumulate() { + + JSONObject jsonObject = new JSONObject(); + jsonObject.accumulate("myArray", true); + jsonObject.accumulate("myArray", false); + jsonObject.accumulate("myArray", "hello world!"); + jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!"); + jsonObject.accumulate("myArray", 42); + jsonObject.accumulate("myArray", -23.45e7); + // include an unsupported object for coverage + try { + jsonObject.accumulate("myArray", Double.NaN); + fail("Expected exception"); + } catch (JSONException ignored) {} + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); + assertTrue("expected 6 myArray items", ((List)(JsonPath.read(doc, "$.myArray"))).size() == 6); + assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0"))); + assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1"))); + assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2"))); + assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3"))); + assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4"))); + assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5"))); + } + + /** + * Exercise the JSONObject append() functionality + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectAppend() { + JSONObject jsonObject = new JSONObject(); + jsonObject.append("myArray", true); + jsonObject.append("myArray", false); + jsonObject.append("myArray", "hello world!"); + jsonObject.append("myArray", "h\be\tllo w\u1234orld!"); + jsonObject.append("myArray", 42); + jsonObject.append("myArray", -23.45e7); + // include an unsupported object for coverage + try { + jsonObject.append("myArray", Double.NaN); + fail("Expected exception"); + } catch (JSONException ignored) {} + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); + assertTrue("expected 6 myArray items", ((List)(JsonPath.read(doc, "$.myArray"))).size() == 6); + assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0"))); + assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1"))); + assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2"))); + assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3"))); + assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4"))); + assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5"))); + } + + /** + * Exercise the JSONObject doubleToString() method + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectDoubleToString() { + String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" }; + Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67, + Double.NaN, Double.NEGATIVE_INFINITY }; + for (int i = 0; i < expectedStrs.length; ++i) { + String actualStr = JSONObject.doubleToString(doubles[i]); + assertTrue("value expected ["+expectedStrs[i]+ + "] found ["+actualStr+ "]", + expectedStrs[i].equals(actualStr)); + } + } + + /** + * Exercise some JSONObject get[type] and opt[type] methods + */ + @Test + public void jsonObjectValues() { + String str = + "{"+ + "\"trueKey\":true,"+ + "\"falseKey\":false,"+ + "\"trueStrKey\":\"true\","+ + "\"falseStrKey\":\"false\","+ + "\"stringKey\":\"hello world!\","+ + "\"intKey\":42,"+ + "\"intStrKey\":\"43\","+ + "\"longKey\":1234567890123456789,"+ + "\"longStrKey\":\"987654321098765432\","+ + "\"doubleKey\":-23.45e7,"+ + "\"doubleStrKey\":\"00001.000\","+ + "\"BigDecimalStrKey\":\"19007199254740993.35481234487103587486413587843213584\","+ + "\"negZeroKey\":-0.0,"+ + "\"negZeroStrKey\":\"-0.0\","+ + "\"arrayKey\":[0,1,2],"+ + "\"objectKey\":{\"myKey\":\"myVal\"}"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey")); + assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey")); + assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey")); + assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey")); + assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey")); + assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey")); + assertTrue("stringKey should be string", + jsonObject.getString("stringKey").equals("hello world!")); + assertTrue("doubleKey should be double", + jsonObject.getDouble("doubleKey") == -23.45e7); + assertTrue("doubleStrKey should be double", + jsonObject.getDouble("doubleStrKey") == 1); + assertTrue("doubleKey can be float", + jsonObject.getFloat("doubleKey") == -23.45e7f); + assertTrue("doubleStrKey can be float", + jsonObject.getFloat("doubleStrKey") == 1f); + assertTrue("opt doubleKey should be double", + jsonObject.optDouble("doubleKey") == -23.45e7); + assertTrue("opt doubleKey with Default should be double", + jsonObject.optDouble("doubleStrKey", Double.NaN) == 1); + assertTrue("opt negZeroKey should be a Double", + jsonObject.opt("negZeroKey") instanceof Double); + assertTrue("get negZeroKey should be a Double", + jsonObject.get("negZeroKey") instanceof Double); + assertTrue("optNumber negZeroKey should return Double", + jsonObject.optNumber("negZeroKey") instanceof Double); + assertTrue("optNumber negZeroStrKey should return Double", + jsonObject.optNumber("negZeroStrKey") instanceof Double); + assertTrue("opt negZeroKey should be double", + Double.compare(jsonObject.optDouble("negZeroKey"), -0.0d) == 0); + assertTrue("opt negZeroStrKey with Default should be double", + Double.compare(jsonObject.optDouble("negZeroStrKey"), -0.0d) == 0); + assertTrue("optNumber negZeroKey should be -0.0", + Double.compare(jsonObject.optNumber("negZeroKey").doubleValue(), -0.0d) == 0); + assertTrue("optNumber negZeroStrKey should be -0.0", + Double.compare(jsonObject.optNumber("negZeroStrKey").doubleValue(), -0.0d) == 0); + assertTrue("optFloat doubleKey should be float", + jsonObject.optFloat("doubleKey") == -23.45e7f); + assertTrue("optFloat doubleKey with Default should be float", + jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f); + assertTrue("intKey should be int", + jsonObject.optInt("intKey") == 42); + assertTrue("opt intKey should be int", + jsonObject.optInt("intKey", 0) == 42); + assertTrue("opt intKey with default should be int", + jsonObject.getInt("intKey") == 42); + assertTrue("intStrKey should be int", + jsonObject.getInt("intStrKey") == 43); + assertTrue("longKey should be long", + jsonObject.getLong("longKey") == 1234567890123456789L); + assertTrue("opt longKey should be long", + jsonObject.optLong("longKey") == 1234567890123456789L); + assertTrue("opt longKey with default should be long", + jsonObject.optLong("longKey", 0) == 1234567890123456789L); + assertTrue("longStrKey should be long", + jsonObject.getLong("longStrKey") == 987654321098765432L); + assertTrue("optNumber int should return Integer", + jsonObject.optNumber("intKey") instanceof Integer); + assertTrue("optNumber long should return Long", + jsonObject.optNumber("longKey") instanceof Long); + assertTrue("optNumber double should return Double", + jsonObject.optNumber("doubleKey") instanceof Double); + assertTrue("optNumber Str int should return Integer", + jsonObject.optNumber("intStrKey") instanceof Integer); + assertTrue("optNumber Str long should return Long", + jsonObject.optNumber("longStrKey") instanceof Long); + assertTrue("optNumber Str double should return Double", + jsonObject.optNumber("doubleStrKey") instanceof Double); + assertTrue("optNumber BigDecimalStrKey should return BigDecimal", + jsonObject.optNumber("BigDecimalStrKey") instanceof BigDecimal); + assertTrue("xKey should not exist", + jsonObject.isNull("xKey")); + assertTrue("stringKey should exist", + jsonObject.has("stringKey")); + assertTrue("opt stringKey should string", + jsonObject.optString("stringKey").equals("hello world!")); + assertTrue("opt stringKey with default should string", + jsonObject.optString("stringKey", "not found").equals("hello world!")); + JSONArray jsonArray = jsonObject.getJSONArray("arrayKey"); + assertTrue("arrayKey should be JSONArray", + jsonArray.getInt(0) == 0 && + jsonArray.getInt(1) == 1 && + jsonArray.getInt(2) == 2); + jsonArray = jsonObject.optJSONArray("arrayKey"); + assertTrue("opt arrayKey should be JSONArray", + jsonArray.getInt(0) == 0 && + jsonArray.getInt(1) == 1 && + jsonArray.getInt(2) == 2); + JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey"); + assertTrue("objectKey should be JSONObject", + jsonObjectInner.get("myKey").equals("myVal")); + } + + /** + * Check whether JSONObject handles large or high precision numbers correctly + */ + @Test + public void stringToValueNumbersTest() { + assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double); + assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double); + assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String); + assertTrue( "0.2 should be a Double!", + JSONObject.stringToValue( "0.2" ) instanceof Double ); + assertTrue( "Doubles should be Doubles, even when incorrectly converting floats!", + JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof Double ); + /** + * This test documents a need for BigDecimal conversion. + */ + Object obj = JSONObject.stringToValue( "299792.457999999984" ); + assertTrue( "evaluates to 299792.458 double instead of 299792.457999999984 BigDecimal!", + obj.equals(new Double(299792.458)) ); + assertTrue( "1 should be an Integer!", + JSONObject.stringToValue( "1" ) instanceof Integer ); + assertTrue( "Integer.MAX_VALUE should still be an Integer!", + JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer ); + assertTrue( "Large integers should be a Long!", + JSONObject.stringToValue( new Long( Long.sum( Integer.MAX_VALUE, 1 ) ).toString() ) instanceof Long ); + assertTrue( "Long.MAX_VALUE should still be an Integer!", + JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long ); + + String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString(); + assertTrue( "Really large integers currently evaluate to string", + JSONObject.stringToValue(str).equals("9223372036854775808")); + } + + /** + * This test documents numeric values which could be numerically + * handled as BigDecimal or BigInteger. It helps determine what outputs + * will change if those types are supported. + */ + @Test + public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() { + // Valid JSON Numbers, probably should return BigDecimal or BigInteger objects + String str = + "{"+ + "\"numberWithDecimals\":299792.457999999984,"+ + "\"largeNumber\":12345678901234567890,"+ + "\"preciseNumber\":0.2000000000000000111,"+ + "\"largeExponent\":-23.45e2327"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + // Comes back as a double, but loses precision + assertTrue( "numberWithDecimals currently evaluates to double 299792.458", + jsonObject.get( "numberWithDecimals" ).equals( new Double( "299792.458" ) ) ); + Object obj = jsonObject.get( "largeNumber" ); + assertTrue("largeNumber currently evaluates to string", + "12345678901234567890".equals(obj)); + // comes back as a double but loses precision + assertTrue( "preciseNumber currently evaluates to double 0.2", + jsonObject.get( "preciseNumber" ).equals(new Double(0.2))); + obj = jsonObject.get( "largeExponent" ); + assertTrue("largeExponent should currently evaluates as a string", + "-23.45e2327".equals(obj)); + } + + /** + * This test documents how JSON-Java handles invalid numeric input. + */ + @Test + public void jsonInvalidNumberValues() { + // Number-notations supported by Java and invalid as JSON + String str = + "{"+ + "\"hexNumber\":-0x123,"+ + "\"tooManyZeros\":00,"+ + "\"negativeInfinite\":-Infinity,"+ + "\"negativeNaN\":-NaN,"+ + "\"negativeFraction\":-.01,"+ + "\"tooManyZerosFraction\":00.001,"+ + "\"negativeHexFloat\":-0x1.fffp1,"+ + "\"hexFloat\":0x1.0P-1074,"+ + "\"floatIdentifier\":0.1f,"+ + "\"doubleIdentifier\":0.1d"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + Object obj; + obj = jsonObject.get( "hexNumber" ); + assertFalse( "hexNumber must not be a number (should throw exception!?)", + obj instanceof Number ); + assertTrue("hexNumber currently evaluates to string", + obj.equals("-0x123")); + assertTrue( "tooManyZeros currently evaluates to string", + jsonObject.get( "tooManyZeros" ).equals("00")); + obj = jsonObject.get("negativeInfinite"); + assertTrue( "negativeInfinite currently evaluates to string", + obj.equals("-Infinity")); + obj = jsonObject.get("negativeNaN"); + assertTrue( "negativeNaN currently evaluates to string", + obj.equals("-NaN")); + assertTrue( "negativeFraction currently evaluates to double -0.01", + jsonObject.get( "negativeFraction" ).equals(new Double(-0.01))); + assertTrue( "tooManyZerosFraction currently evaluates to double 0.001", + jsonObject.get( "tooManyZerosFraction" ).equals(new Double(0.001))); + assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875", + jsonObject.get( "negativeHexFloat" ).equals(new Double(-3.99951171875))); + assertTrue("hexFloat currently evaluates to double 4.9E-324", + jsonObject.get("hexFloat").equals(new Double(4.9E-324))); + assertTrue("floatIdentifier currently evaluates to double 0.1", + jsonObject.get("floatIdentifier").equals(new Double(0.1))); + assertTrue("doubleIdentifier currently evaluates to double 0.1", + jsonObject.get("doubleIdentifier").equals(new Double(0.1))); + } + + /** + * Tests how JSONObject get[type] handles incorrect types + */ + @Test + public void jsonObjectNonAndWrongValues() { + String str = + "{"+ + "\"trueKey\":true,"+ + "\"falseKey\":false,"+ + "\"trueStrKey\":\"true\","+ + "\"falseStrKey\":\"false\","+ + "\"stringKey\":\"hello world!\","+ + "\"intKey\":42,"+ + "\"intStrKey\":\"43\","+ + "\"longKey\":1234567890123456789,"+ + "\"longStrKey\":\"987654321098765432\","+ + "\"doubleKey\":-23.45e7,"+ + "\"doubleStrKey\":\"00001.000\","+ + "\"arrayKey\":[0,1,2],"+ + "\"objectKey\":{\"myKey\":\"myVal\"}"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + try { + jsonObject.getBoolean("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("expecting an exception message", + "JSONObject[\"nonKey\"] not found.", e.getMessage()); + } + try { + jsonObject.getBoolean("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a Boolean.", + e.getMessage()); + } + try { + jsonObject.getString("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getString("trueKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"trueKey\"] is not a string.", + e.getMessage()); + } + try { + jsonObject.getDouble("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getDouble("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a double.", + e.getMessage()); + } + try { + jsonObject.getFloat("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getFloat("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a float.", + e.getMessage()); + } + try { + jsonObject.getInt("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getInt("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a int.", + e.getMessage()); + } + try { + jsonObject.getLong("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getLong("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a long.", + e.getMessage()); + } + try { + jsonObject.getJSONArray("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getJSONArray("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a JSONArray.", + e.getMessage()); + } + try { + jsonObject.getJSONObject("nonKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"nonKey\"] not found.", + e.getMessage()); + } + try { + jsonObject.getJSONObject("stringKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"stringKey\"] is not a JSONObject.", + e.getMessage()); + } + } + + /** + * This test documents an unexpected numeric behavior. + * A double that ends with .0 is parsed, serialized, then + * parsed again. On the second parse, it has become an int. + */ + @Test + public void unexpectedDoubleToIntConversion() { + String key30 = "key30"; + String key31 = "key31"; + JSONObject jsonObject = new JSONObject(); + jsonObject.put(key30, new Double(3.0)); + jsonObject.put(key31, new Double(3.1)); + + assertTrue("3.0 should remain a double", + jsonObject.getDouble(key30) == 3); + assertTrue("3.1 should remain a double", + jsonObject.getDouble(key31) == 3.1); + + // turns 3.0 into 3. + String serializedString = jsonObject.toString(); + JSONObject deserialized = new JSONObject(serializedString); + assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer); + assertTrue("3.0 can still be interpreted as a double", + deserialized.getDouble(key30) == 3.0); + assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1); + } + + /** + * Document behaviors of big numbers. Includes both JSONObject + * and JSONArray tests + */ + @SuppressWarnings("boxing") + @Test + public void bigNumberOperations() { + /** + * JSONObject tries to parse BigInteger as a bean, but it only has + * one getter, getLowestBitSet(). The value is lost and an unhelpful + * value is stored. This should be fixed. + */ + BigInteger bigInteger = new BigInteger("123456789012345678901234567890"); + JSONObject jsonObject = new JSONObject(bigInteger); + Object obj = jsonObject.get("lowestSetBit"); + assertTrue("JSONObject only has 1 value", jsonObject.length() == 1); + assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet", + obj instanceof Integer); + assertTrue("this bigInteger lowestBitSet happens to be 1", + obj.equals(1)); + + /** + * JSONObject tries to parse BigDecimal as a bean, but it has + * no getters, The value is lost and no value is stored. + * This should be fixed. + */ + BigDecimal bigDecimal = new BigDecimal( + "123456789012345678901234567890.12345678901234567890123456789"); + jsonObject = new JSONObject(bigDecimal); + assertTrue("large bigDecimal is not stored", jsonObject.isEmpty()); + + /** + * JSONObject put(String, Object) method stores and serializes + * bigInt and bigDec correctly. Nothing needs to change. + */ + jsonObject = new JSONObject(); + jsonObject.put("bigInt", bigInteger); + assertTrue("jsonObject.put() handles bigInt correctly", + jsonObject.get("bigInt").equals(bigInteger)); + assertTrue("jsonObject.getBigInteger() handles bigInt correctly", + jsonObject.getBigInteger("bigInt").equals(bigInteger)); + assertTrue("jsonObject.optBigInteger() handles bigInt correctly", + jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger)); + assertTrue("jsonObject serializes bigInt correctly", + jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}")); + assertTrue("BigInteger as BigDecimal", + jsonObject.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger))); + + + jsonObject = new JSONObject(); + jsonObject.put("bigDec", bigDecimal); + assertTrue("jsonObject.put() handles bigDec correctly", + jsonObject.get("bigDec").equals(bigDecimal)); + assertTrue("jsonObject.getBigDecimal() handles bigDec correctly", + jsonObject.getBigDecimal("bigDec").equals(bigDecimal)); + assertTrue("jsonObject.optBigDecimal() handles bigDec correctly", + jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal)); + assertTrue("jsonObject serializes bigDec correctly", + jsonObject.toString().equals( + "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}")); + + assertTrue("BigDecimal as BigInteger", + jsonObject.getBigInteger("bigDec").equals(bigDecimal.toBigInteger())); + /** + * exercise some exceptions + */ + try { + // bigInt key does not exist + jsonObject.getBigDecimal("bigInt"); + fail("expected an exeption"); + } catch (JSONException ignored) {} + obj = jsonObject.optBigDecimal("bigInt", BigDecimal.ONE); + assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE)); + jsonObject.put("stringKey", "abc"); + try { + jsonObject.getBigDecimal("stringKey"); + fail("expected an exeption"); + } catch (JSONException ignored) {} + obj = jsonObject.optBigInteger("bigDec", BigInteger.ONE); + assertTrue("expected BigInteger", obj instanceof BigInteger); + assertEquals(bigDecimal.toBigInteger(), obj); + + /** + * JSONObject.numberToString() works correctly, nothing to change. + */ + String str = JSONObject.numberToString(bigInteger); + assertTrue("numberToString() handles bigInteger correctly", + str.equals("123456789012345678901234567890")); + str = JSONObject.numberToString(bigDecimal); + assertTrue("numberToString() handles bigDecimal correctly", + str.equals("123456789012345678901234567890.12345678901234567890123456789")); + + /** + * JSONObject.stringToValue() turns bigInt into an accurate string, + * and rounds bigDec. This incorrect, but users may have come to + * expect this behavior. Change would be marginally better, but + * might inconvenience users. + */ + obj = JSONObject.stringToValue(bigInteger.toString()); + assertTrue("stringToValue() turns bigInteger string into string", + obj instanceof String); + obj = JSONObject.stringToValue(bigDecimal.toString()); + assertTrue("stringToValue() changes bigDecimal string", + !obj.toString().equals(bigDecimal.toString())); + + /** + * wrap() vs put() big number behavior is now the same. + */ + // bigInt map ctor + Map map = new HashMap(); + map.put("bigInt", bigInteger); + jsonObject = new JSONObject(map); + String actualFromMapStr = jsonObject.toString(); + assertTrue("bigInt in map (or array or bean) is a string", + actualFromMapStr.equals( + "{\"bigInt\":123456789012345678901234567890}")); + // bigInt put + jsonObject = new JSONObject(); + jsonObject.put("bigInt", bigInteger); + String actualFromPutStr = jsonObject.toString(); + assertTrue("bigInt from put is a number", + actualFromPutStr.equals( + "{\"bigInt\":123456789012345678901234567890}")); + // bigDec map ctor + map = new HashMap(); + map.put("bigDec", bigDecimal); + jsonObject = new JSONObject(map); + actualFromMapStr = jsonObject.toString(); + assertTrue("bigDec in map (or array or bean) is a bigDec", + actualFromMapStr.equals( + "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}")); + // bigDec put + jsonObject = new JSONObject(); + jsonObject.put("bigDec", bigDecimal); + actualFromPutStr = jsonObject.toString(); + assertTrue("bigDec from put is a number", + actualFromPutStr.equals( + "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}")); + // bigInt,bigDec put + JSONArray jsonArray = new JSONArray(); + jsonArray.put(bigInteger); + jsonArray.put(bigDecimal); + actualFromPutStr = jsonArray.toString(); + assertTrue("bigInt, bigDec from put is a number", + actualFromPutStr.equals( + "[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]")); + assertTrue("getBigInt is bigInt", jsonArray.getBigInteger(0).equals(bigInteger)); + assertTrue("getBigDec is bigDec", jsonArray.getBigDecimal(1).equals(bigDecimal)); + assertTrue("optBigInt is bigInt", jsonArray.optBigInteger(0, BigInteger.ONE).equals(bigInteger)); + assertTrue("optBigDec is bigDec", jsonArray.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal)); + jsonArray.put(Boolean.TRUE); + try { + jsonArray.getBigInteger(2); + fail("should not be able to get big int"); + } catch (Exception ignored) {} + try { + jsonArray.getBigDecimal(2); + fail("should not be able to get big dec"); + } catch (Exception ignored) {} + assertTrue("optBigInt is default", jsonArray.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE)); + assertTrue("optBigDec is default", jsonArray.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE)); + + // bigInt,bigDec list ctor + List list = new ArrayList(); + list.add(bigInteger); + list.add(bigDecimal); + jsonArray = new JSONArray(list); + String actualFromListStr = jsonArray.toString(); + assertTrue("bigInt, bigDec in list is a bigInt, bigDec", + actualFromListStr.equals( + "[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]")); + // bigInt bean ctor + MyBigNumberBean myBigNumberBean = mock(MyBigNumberBean.class); + when(myBigNumberBean.getBigInteger()).thenReturn(new BigInteger("123456789012345678901234567890")); + jsonObject = new JSONObject(myBigNumberBean); + String actualFromBeanStr = jsonObject.toString(); + // can't do a full string compare because mockery adds an extra key/value + assertTrue("bigInt from bean ctor is a bigInt", + actualFromBeanStr.contains("123456789012345678901234567890")); + // bigDec bean ctor + myBigNumberBean = mock(MyBigNumberBean.class); + when(myBigNumberBean.getBigDecimal()).thenReturn(new BigDecimal("123456789012345678901234567890.12345678901234567890123456789")); + jsonObject = new JSONObject(myBigNumberBean); + actualFromBeanStr = jsonObject.toString(); + // can't do a full string compare because mockery adds an extra key/value + assertTrue("bigDec from bean ctor is a bigDec", + actualFromBeanStr.contains("123456789012345678901234567890.12345678901234567890123456789")); + // bigInt,bigDec wrap() + obj = JSONObject.wrap(bigInteger); + assertTrue("wrap() returns big num",obj.equals(bigInteger)); + obj = JSONObject.wrap(bigDecimal); + assertTrue("wrap() returns string",obj.equals(bigDecimal)); + + } + + /** + * The purpose for the static method getNames() methods are not clear. + * This method is not called from within JSON-Java. Most likely + * uses are to prep names arrays for: + * JSONObject(JSONObject jo, String[] names) + * JSONObject(Object object, String names[]), + */ + @Test + public void jsonObjectNames() { + JSONObject jsonObject; + + // getNames() from null JSONObject + assertTrue("null names from null Object", + null == JSONObject.getNames((Object)null)); + + // getNames() from object with no fields + assertTrue("null names from Object with no fields", + null == JSONObject.getNames(new MyJsonString())); + + // getNames from new JSONOjbect + jsonObject = new JSONObject(); + String [] names = JSONObject.getNames(jsonObject); + assertTrue("names should be null", names == null); + + + // getNames() from empty JSONObject + String emptyStr = "{}"; + jsonObject = new JSONObject(emptyStr); + assertTrue("empty JSONObject should have null names", + null == JSONObject.getNames(jsonObject)); + + // getNames() from JSONObject + String str = + "{"+ + "\"trueKey\":true,"+ + "\"falseKey\":false,"+ + "\"stringKey\":\"hello world!\","+ + "}"; + jsonObject = new JSONObject(str); + names = JSONObject.getNames(jsonObject); + JSONArray jsonArray = new JSONArray(names); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider() + .parse(jsonArray.toString()); + List docList = JsonPath.read(doc, "$"); + assertTrue("expected 3 items", docList.size() == 3); + assertTrue( + "expected to find trueKey", + ((List) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1); + assertTrue( + "expected to find falseKey", + ((List) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1); + assertTrue( + "expected to find stringKey", + ((List) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1); + + /** + * getNames() from an enum with properties has an interesting result. + * It returns the enum values, not the selected enum properties + */ + MyEnumField myEnumField = MyEnumField.VAL1; + names = JSONObject.getNames(myEnumField); + + // validate JSON + jsonArray = new JSONArray(names); + doc = Configuration.defaultConfiguration().jsonProvider() + .parse(jsonArray.toString()); + docList = JsonPath.read(doc, "$"); + assertTrue("expected 3 items", docList.size() == 3); + assertTrue( + "expected to find VAL1", + ((List) JsonPath.read(doc, "$[?(@=='VAL1')]")).size() == 1); + assertTrue( + "expected to find VAL2", + ((List) JsonPath.read(doc, "$[?(@=='VAL2')]")).size() == 1); + assertTrue( + "expected to find VAL3", + ((List) JsonPath.read(doc, "$[?(@=='VAL3')]")).size() == 1); + + /** + * A bean is also an object. But in order to test the static + * method getNames(), this particular bean needs some public + * data members. + */ + MyPublicClass myPublicClass = new MyPublicClass(); + names = JSONObject.getNames(myPublicClass); + + // validate JSON + jsonArray = new JSONArray(names); + doc = Configuration.defaultConfiguration().jsonProvider() + .parse(jsonArray.toString()); + docList = JsonPath.read(doc, "$"); + assertTrue("expected 2 items", docList.size() == 2); + assertTrue( + "expected to find publicString", + ((List) JsonPath.read(doc, "$[?(@=='publicString')]")).size() == 1); + assertTrue( + "expected to find publicInt", + ((List) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1); + } + + /** + * Populate a JSONArray from an empty JSONObject names() method. + * It should be empty. + */ + @Test + public void emptyJsonObjectNamesToJsonAray() { + JSONObject jsonObject = new JSONObject(); + JSONArray jsonArray = jsonObject.names(); + assertTrue("jsonArray should be null", jsonArray == null); + } + + /** + * Populate a JSONArray from a JSONObject names() method. + * Confirm that it contains the expected names. + */ + @Test + public void jsonObjectNamesToJsonAray() { + String str = + "{"+ + "\"trueKey\":true,"+ + "\"falseKey\":false,"+ + "\"stringKey\":\"hello world!\","+ + "}"; + + JSONObject jsonObject = new JSONObject(str); + JSONArray jsonArray = jsonObject.names(); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString()); + assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); + assertTrue("expected to find trueKey", ((List) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1); + assertTrue("expected to find falseKey", ((List) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1); + assertTrue("expected to find stringKey", ((List) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1); + } + + /** + * Exercise the JSONObject increment() method. + */ + @SuppressWarnings("cast") + @Test + public void jsonObjectIncrement() { + String str = + "{"+ + "\"keyLong\":9999999991,"+ + "\"keyDouble\":1.1"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + jsonObject.increment("keyInt"); + jsonObject.increment("keyInt"); + jsonObject.increment("keyLong"); + jsonObject.increment("keyDouble"); + jsonObject.increment("keyInt"); + jsonObject.increment("keyLong"); + jsonObject.increment("keyDouble"); + /** + * JSONObject constructor won't handle these types correctly, but + * adding them via put works. + */ + jsonObject.put("keyFloat", 1.1f); + jsonObject.put("keyBigInt", new BigInteger("123456789123456789123456789123456780")); + jsonObject.put("keyBigDec", new BigDecimal("123456789123456789123456789123456780.1")); + jsonObject.increment("keyFloat"); + jsonObject.increment("keyFloat"); + jsonObject.increment("keyBigInt"); + jsonObject.increment("keyBigDec"); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 6 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 6); + assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/keyInt"))); + assertTrue("expected 9999999993", Long.valueOf(9999999993L).equals(jsonObject.query("/keyLong"))); + assertTrue("expected 3.1", Double.valueOf(3.1).equals(jsonObject.query("/keyDouble"))); + assertTrue("expected 123456789123456789123456789123456781", new BigInteger("123456789123456789123456789123456781").equals(jsonObject.query("/keyBigInt"))); + assertTrue("expected 123456789123456789123456789123456781.1", new BigDecimal("123456789123456789123456789123456781.1").equals(jsonObject.query("/keyBigDec"))); + + /** + * Should work the same way on any platform! @see https://docs.oracle + * .com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3 This is the + * effect of a float to double conversion and is inherent to the + * shortcomings of the IEEE 754 format, when converting 32-bit into + * double-precision 64-bit. Java type-casts float to double. A 32 bit + * float is type-casted to 64 bit double by simply appending zero-bits + * to the mantissa (and extended the signed exponent by 3 bits.) and + * there is no way to obtain more information than it is stored in the + * 32-bits float. + * + * Like 1/3 cannot be represented as base10 number because it is + * periodically, 1/5 (for example) cannot be represented as base2 number + * since it is periodically in base2 (take a look at + * http://www.h-schmidt.net/FloatConverter/) The same happens to 3.1, + * that decimal number (base10 representation) is periodic in base2 + * representation, therefore appending zero-bits is inaccurate. Only + * repeating the periodically occurring bits (0110) would be a proper + * conversion. However one cannot detect from a 32 bit IEE754 + * representation which bits would "repeat infinitely", since the + * missing bits would not fit into the 32 bit float, i.e. the + * information needed simply is not there! + */ + assertEquals(Float.valueOf(3.1f), jsonObject.query("/keyFloat")); + + /** + * float f = 3.1f; double df = (double) f; double d = 3.1d; + * System.out.println + * (Integer.toBinaryString(Float.floatToRawIntBits(f))); + * System.out.println + * (Long.toBinaryString(Double.doubleToRawLongBits(df))); + * System.out.println + * (Long.toBinaryString(Double.doubleToRawLongBits(d))); + * + * - Float: + * seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm + * 1000000010001100110011001100110 + * - Double + * seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + * 10000000 10001100110011001100110 + * 100000000001000110011001100110011000000000000000000000000000000 + * 100000000001000110011001100110011001100110011001100110011001101 + */ + + /** + * Examples of well documented but probably unexpected behavior in + * java / with 32-bit float to 64-bit float conversion. + */ + assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double)0.2f == 0.2d ); + assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d ); + Double d1 = new Double( 1.1f ); + Double d2 = new Double( "1.1f" ); + assertFalse( "Document implicit type cast from float to double before calling Double(double d) constructor", d1.equals( d2 ) ); + + assertTrue( "Correctly converting float to double via base10 (string) representation!", new Double( 3.1d ).equals( new Double( new Float( 3.1f ).toString() ) ) ); + + // Pinpointing the not so obvious "buggy" conversion from float to double in JSONObject + JSONObject jo = new JSONObject(); + jo.put( "bug", 3.1f ); // will call put( String key, double value ) with implicit and "buggy" type-cast from float to double + assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( new Double( 3.1d ) ) ); + + JSONObject inc = new JSONObject(); + inc.put( "bug", new Float( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value ) + assertTrue( "Everything is ok here!", inc.get( "bug" ) instanceof Float ); + inc.increment( "bug" ); // after adding 1, increment will call put( String key, double value ) with implicit and "buggy" type-cast from float to double! + // this.put(key, (Float) value + 1); + // 1. The (Object)value will be typecasted to (Float)value since it is an instanceof Float actually nothing is done. + // 2. Float instance will be autoboxed into float because the + operator will work on primitives not Objects! + // 3. A float+float operation will be performed and results into a float primitive. + // 4. There is no method that matches the signature put( String key, float value), java-compiler will choose the method + // put( String key, double value) and does an implicit type-cast(!) by appending zero-bits to the mantissa + assertTrue( "JSONObject increment converts Float to Double", jo.get( "bug" ) instanceof Float ); + // correct implementation (with change of behavior) would be: + // this.put(key, new Float((Float) value + 1)); + // Probably it would be better to deprecate the method and remove some day, while convenient processing the "payload" is not + // really in the the scope of a JSON-library (IMHO.) + + } + + /** + * Exercise JSONObject numberToString() method + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectNumberToString() { + String str; + Double dVal; + Integer iVal = 1; + str = JSONObject.numberToString(iVal); + assertTrue("expected "+iVal+" actual "+str, iVal.toString().equals(str)); + dVal = 12.34; + str = JSONObject.numberToString(dVal); + assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str)); + dVal = 12.34e27; + str = JSONObject.numberToString(dVal); + assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str)); + // trailing .0 is truncated, so it doesn't quite match toString() + dVal = 5000000.0000000; + str = JSONObject.numberToString(dVal); + assertTrue("expected 5000000 actual "+str, str.equals("5000000")); + } + + /** + * Exercise JSONObject put() and similar() methods + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectPut() { + String expectedStr = + "{"+ + "\"trueKey\":true,"+ + "\"falseKey\":false,"+ + "\"arrayKey\":[0,1,2],"+ + "\"objectKey\":{"+ + "\"myKey1\":\"myVal1\","+ + "\"myKey2\":\"myVal2\","+ + "\"myKey3\":\"myVal3\","+ + "\"myKey4\":\"myVal4\""+ + "}"+ + "}"; + JSONObject jsonObject = new JSONObject(); + jsonObject.put("trueKey", true); + jsonObject.put("falseKey", false); + Integer [] intArray = { 0, 1, 2 }; + jsonObject.put("arrayKey", Arrays.asList(intArray)); + Map myMap = new HashMap(); + myMap.put("myKey1", "myVal1"); + myMap.put("myKey2", "myVal2"); + myMap.put("myKey3", "myVal3"); + myMap.put("myKey4", "myVal4"); + jsonObject.put("objectKey", myMap); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 4 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 4); + assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); + assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); + assertTrue("expected 3 arrayKey items", ((List)(JsonPath.read(doc, "$.arrayKey"))).size() == 3); + assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0"))); + assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1"))); + assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2"))); + assertTrue("expected 4 objectKey items", ((Map)(JsonPath.read(doc, "$.objectKey"))).size() == 4); + assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1"))); + assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2"))); + assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3"))); + assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4"))); + + jsonObject.remove("trueKey"); + JSONObject expectedJsonObject = new JSONObject(expectedStr); + assertTrue("unequal jsonObjects should not be similar", + !jsonObject.similar(expectedJsonObject)); + assertTrue("jsonObject should not be similar to jsonArray", + !jsonObject.similar(new JSONArray())); + + String aCompareValueStr = "{\"a\":\"aval\",\"b\":true}"; + String bCompareValueStr = "{\"a\":\"notAval\",\"b\":true}"; + JSONObject aCompareValueJsonObject = new JSONObject(aCompareValueStr); + JSONObject bCompareValueJsonObject = new JSONObject(bCompareValueStr); + assertTrue("different values should not be similar", + !aCompareValueJsonObject.similar(bCompareValueJsonObject)); + + String aCompareObjectStr = "{\"a\":\"aval\",\"b\":{}}"; + String bCompareObjectStr = "{\"a\":\"aval\",\"b\":true}"; + JSONObject aCompareObjectJsonObject = new JSONObject(aCompareObjectStr); + JSONObject bCompareObjectJsonObject = new JSONObject(bCompareObjectStr); + assertTrue("different nested JSONObjects should not be similar", + !aCompareObjectJsonObject.similar(bCompareObjectJsonObject)); + + String aCompareArrayStr = "{\"a\":\"aval\",\"b\":[]}"; + String bCompareArrayStr = "{\"a\":\"aval\",\"b\":true}"; + JSONObject aCompareArrayJsonObject = new JSONObject(aCompareArrayStr); + JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr); + assertTrue("different nested JSONArrays should not be similar", + !aCompareArrayJsonObject.similar(bCompareArrayJsonObject)); + } + + /** + * Exercise JSONObject toString() method + */ + @Test + public void jsonObjectToString() { + String str = + "{"+ + "\"trueKey\":true,"+ + "\"falseKey\":false,"+ + "\"arrayKey\":[0,1,2],"+ + "\"objectKey\":{"+ + "\"myKey1\":\"myVal1\","+ + "\"myKey2\":\"myVal2\","+ + "\"myKey3\":\"myVal3\","+ + "\"myKey4\":\"myVal4\""+ + "}"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 4 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 4); + assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey"))); + assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey"))); + assertTrue("expected 3 arrayKey items", ((List)(JsonPath.read(doc, "$.arrayKey"))).size() == 3); + assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0"))); + assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1"))); + assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2"))); + assertTrue("expected 4 objectKey items", ((Map)(JsonPath.read(doc, "$.objectKey"))).size() == 4); + assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1"))); + assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2"))); + assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3"))); + assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4"))); + } + + /** + * Exercise JSONObject toString() method with various indent levels. + */ + @Test + public void jsonObjectToStringIndent() { + String jsonObject0Str = + "{"+ + "\"key1\":" + + "[1,2," + + "{\"key3\":true}" + + "],"+ + "\"key2\":" + + "{\"key1\":\"val1\",\"key2\":" + + "{\"key2\":\"val2\"}" + + "},"+ + "\"key3\":" + + "[" + + "[1,2.1]" + + "," + + "[null]" + + "]"+ + "}"; + + String jsonObject1Str = + "{\n" + + " \"key1\": [\n" + + " 1,\n" + + " 2,\n" + + " {\"key3\": true}\n" + + " ],\n" + + " \"key2\": {\n" + + " \"key1\": \"val1\",\n" + + " \"key2\": {\"key2\": \"val2\"}\n" + + " },\n" + + " \"key3\": [\n" + + " [\n" + + " 1,\n" + + " 2.1\n" + + " ],\n" + + " [null]\n" + + " ]\n" + + "}"; + String jsonObject4Str = + "{\n" + + " \"key1\": [\n" + + " 1,\n" + + " 2,\n" + + " {\"key3\": true}\n" + + " ],\n" + + " \"key2\": {\n" + + " \"key1\": \"val1\",\n" + + " \"key2\": {\"key2\": \"val2\"}\n" + + " },\n" + + " \"key3\": [\n" + + " [\n" + + " 1,\n" + + " 2.1\n" + + " ],\n" + + " [null]\n" + + " ]\n" + + "}"; + JSONObject jsonObject = new JSONObject(jsonObject0Str); + assertEquals("toString()",jsonObject0Str, jsonObject.toString()); + assertEquals("toString(0)",jsonObject0Str, jsonObject.toString(0)); + assertEquals("toString(1)",jsonObject1Str, jsonObject.toString(1)); + assertEquals("toString(4)",jsonObject4Str, jsonObject.toString(4)); + + JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject())); + assertEquals("toString(2)","{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2)); + } + + /** + * Explores how JSONObject handles maps. Insert a string/string map + * as a value in a JSONObject. It will remain a map. Convert the + * JSONObject to string, then create a new JSONObject from the string. + * In the new JSONObject, the value will be stored as a nested JSONObject. + * Confirm that map and nested JSONObject have the same contents. + */ + @Test + public void jsonObjectToStringSuppressWarningOnCastToMap() { + JSONObject jsonObject = new JSONObject(); + Map map = new HashMap<>(); + map.put("abc", "def"); + jsonObject.put("key", map); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); + assertTrue("expected 1 key item", ((Map)(JsonPath.read(doc, "$.key"))).size() == 1); + assertTrue("expected def", "def".equals(jsonObject.query("/key/abc"))); + } + + /** + * Explores how JSONObject handles collections. Insert a string collection + * as a value in a JSONObject. It will remain a collection. Convert the + * JSONObject to string, then create a new JSONObject from the string. + * In the new JSONObject, the value will be stored as a nested JSONArray. + * Confirm that collection and nested JSONArray have the same contents. + */ + @Test + public void jsonObjectToStringSuppressWarningOnCastToCollection() { + JSONObject jsonObject = new JSONObject(); + Collection collection = new ArrayList(); + collection.add("abc"); + // ArrayList will be added as an object + jsonObject.put("key", collection); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString()); + assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); + assertTrue("expected 1 key item", ((List)(JsonPath.read(doc, "$.key"))).size() == 1); + assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0"))); + } + + /** + * Exercises the JSONObject.valueToString() method for various types + */ + @Test + public void valueToString() { + + assertTrue("null valueToString() incorrect", + "null".equals(JSONObject.valueToString(null))); + MyJsonString jsonString = new MyJsonString(); + assertTrue("jsonstring valueToString() incorrect", + "my string".equals(JSONObject.valueToString(jsonString))); + assertTrue("boolean valueToString() incorrect", + "true".equals(JSONObject.valueToString(Boolean.TRUE))); + assertTrue("non-numeric double", + "null".equals(JSONObject.doubleToString(Double.POSITIVE_INFINITY))); + String jsonObjectStr = + "{"+ + "\"key1\":\"val1\","+ + "\"key2\":\"val2\","+ + "\"key3\":\"val3\""+ + "}"; + JSONObject jsonObject = new JSONObject(jsonObjectStr); + assertTrue("jsonObject valueToString() incorrect", + JSONObject.valueToString(jsonObject).equals(jsonObject.toString())); + String jsonArrayStr = + "[1,2,3]"; + JSONArray jsonArray = new JSONArray(jsonArrayStr); + assertTrue("jsonArray valueToString() incorrect", + JSONObject.valueToString(jsonArray).equals(jsonArray.toString())); + Map map = new HashMap(); + map.put("key1", "val1"); + map.put("key2", "val2"); + map.put("key3", "val3"); + assertTrue("map valueToString() incorrect", + jsonObject.toString().equals(JSONObject.valueToString(map))); + Collection collection = new ArrayList(); + collection.add(new Integer(1)); + collection.add(new Integer(2)); + collection.add(new Integer(3)); + assertTrue("collection valueToString() expected: "+ + jsonArray.toString()+ " actual: "+ + JSONObject.valueToString(collection), + jsonArray.toString().equals(JSONObject.valueToString(collection))); + Integer[] array = { new Integer(1), new Integer(2), new Integer(3) }; + assertTrue("array valueToString() incorrect", + jsonArray.toString().equals(JSONObject.valueToString(array))); + } + + /** + * Confirm that https://github.com/douglascrockford/JSON-java/issues/167 is fixed. + * The following code was throwing a ClassCastException in the + * JSONObject(Map) constructor + */ + @SuppressWarnings("boxing") + @Test + public void valueToStringConfirmException() { + Map myMap = new HashMap(); + myMap.put(1, "myValue"); + // this is the test, it should not throw an exception + String str = JSONObject.valueToString(myMap); + // confirm result, just in case + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(str); + assertTrue("expected 1 top level item", ((Map)(JsonPath.read(doc, "$"))).size() == 1); + assertTrue("expected myValue", "myValue".equals(JsonPath.read(doc, "$.1"))); + } + + /** + * Exercise the JSONObject wrap() method. Sometimes wrap() will change + * the object being wrapped, other times not. The purpose of wrap() is + * to ensure the value is packaged in a way that is compatible with how + * a JSONObject value or JSONArray value is supposed to be stored. + */ + @Test + public void wrapObject() { + // wrap(null) returns NULL + assertTrue("null wrap() incorrect", + JSONObject.NULL == JSONObject.wrap(null)); + + // wrap(Integer) returns Integer + Integer in = new Integer(1); + assertTrue("Integer wrap() incorrect", + in == JSONObject.wrap(in)); + + /** + * This test is to document the preferred behavior if BigDecimal is + * supported. Previously bd returned as a string, since it + * is recognized as being a Java package class. Now with explicit + * support for big numbers, it remains a BigDecimal + */ + Object bdWrap = JSONObject.wrap(BigDecimal.ONE); + assertTrue("BigDecimal.ONE evaluates to ONE", + bdWrap.equals(BigDecimal.ONE)); + + // wrap JSONObject returns JSONObject + String jsonObjectStr = + "{"+ + "\"key1\":\"val1\","+ + "\"key2\":\"val2\","+ + "\"key3\":\"val3\""+ + "}"; + JSONObject jsonObject = new JSONObject(jsonObjectStr); + assertTrue("JSONObject wrap() incorrect", + jsonObject == JSONObject.wrap(jsonObject)); + + // wrap collection returns JSONArray + Collection collection = new ArrayList(); + collection.add(new Integer(1)); + collection.add(new Integer(2)); + collection.add(new Integer(3)); + JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection)); + + // validate JSON + Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString()); + assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); + assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0"))); + assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1"))); + assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2"))); + + // wrap Array returns JSONArray + Integer[] array = { new Integer(1), new Integer(2), new Integer(3) }; + JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array)); + + // validate JSON + doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString()); + assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); + assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0"))); + assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1"))); + assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2"))); + + // validate JSON + doc = Configuration.defaultConfiguration().jsonProvider().parse(integerArrayJsonArray.toString()); + assertTrue("expected 3 top level items", ((List)(JsonPath.read(doc, "$"))).size() == 3); + assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0"))); + assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1"))); + assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2"))); + + // wrap map returns JSONObject + Map map = new HashMap(); + map.put("key1", "val1"); + map.put("key2", "val2"); + map.put("key3", "val3"); + JSONObject mapJsonObject = (JSONObject) (JSONObject.wrap(map)); + + // validate JSON + doc = Configuration.defaultConfiguration().jsonProvider().parse(mapJsonObject.toString()); + assertTrue("expected 3 top level items", ((Map)(JsonPath.read(doc, "$"))).size() == 3); + assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1"))); + assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2"))); + assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3"))); + } + + + /** + * RFC 7159 defines control characters to be U+0000 through U+001F. This test verifies that the parser is checking for these in expected ways. + */ + @Test + public void jsonObjectParseControlCharacters(){ + for(int i = 0;i<=0x001f;i++){ + final String charString = String.valueOf((char)i); + final String source = "{\"key\":\""+charString+"\"}"; + try { + JSONObject jo = new JSONObject(source); + assertTrue("Expected "+charString+"("+i+") in the JSON Object but did not find it.",charString.equals(jo.getString("key"))); + } catch (JSONException ex) { + assertTrue("Only \\0 (U+0000), \\n (U+000A), and \\r (U+000D) should cause an error. Instead "+charString+"("+i+") caused an error", + i=='\0' || i=='\n' || i=='\r' + ); + } + } + } + + /** + * Explore how JSONObject handles parsing errors. + */ + @SuppressWarnings("boxing") + @Test + public void jsonObjectParsingErrors() { + try { + // does not start with '{' + String str = "abc"; + assertNull("Expected an exception",new JSONObject(str)); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "A JSONObject text must begin with '{' at 1 [character 2 line 1]", + e.getMessage()); + } + try { + // does not end with '}' + String str = "{"; + assertNull("Expected an exception",new JSONObject(str)); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "A JSONObject text must end with '}' at 1 [character 2 line 1]", + e.getMessage()); + } + try { + // key with no ':' + String str = "{\"myKey\" = true}"; + assertNull("Expected an exception",new JSONObject(str)); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Expected a ':' after a key at 10 [character 11 line 1]", + e.getMessage()); + } + try { + // entries with no ',' separator + String str = "{\"myKey\":true \"myOtherKey\":false}"; + assertNull("Expected an exception",new JSONObject(str)); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Expected a ',' or '}' at 15 [character 16 line 1]", + e.getMessage()); + } + try { + // append to wrong key + String str = "{\"myKey\":true, \"myOtherKey\":false}"; + JSONObject jsonObject = new JSONObject(str); + jsonObject.append("myKey", "hello"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "JSONObject[\"myKey\"] is not a JSONArray (null).", + e.getMessage()); + } + try { + // increment wrong key + String str = "{\"myKey\":true, \"myOtherKey\":false}"; + JSONObject jsonObject = new JSONObject(str); + jsonObject.increment("myKey"); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Unable to increment [\"myKey\"].", + e.getMessage()); + } + try { + // invalid key + String str = "{\"myKey\":true, \"myOtherKey\":false}"; + JSONObject jsonObject = new JSONObject(str); + jsonObject.get(null); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Null key.", + e.getMessage()); + } + try { + // invalid numberToString() + JSONObject.numberToString((Number)null); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Null pointer", + e.getMessage()); + } + + try { + // multiple putOnce key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.putOnce("hello", "world"); + jsonObject.putOnce("hello", "world!"); + fail("Expected an exception"); + } catch (JSONException e) { + assertTrue("", true); + } + try { + // test validity of invalid double + JSONObject.testValidity(Double.NaN); + fail("Expected an exception"); + } catch (JSONException e) { + assertTrue("", true); + } + try { + // test validity of invalid float + JSONObject.testValidity(Float.NEGATIVE_INFINITY); + fail("Expected an exception"); + } catch (JSONException e) { + assertTrue("", true); + } + try { + // test exception message when including a duplicate key (level 0) + String str = "{\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\",\n" + +" \"attr03\":\"value-03\",\n" + +" \"attr03\":\"value-04\"\n" + + "}"; + new JSONObject(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr03\" at 90 [character 13 line 5]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key (level 0) holding an object + String str = "{\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\",\n" + +" \"attr03\":\"value-03\",\n" + +" \"attr03\": {" + +" \"attr04-01\":\"value-04-01\",n" + +" \"attr04-02\":\"value-04-02\",n" + +" \"attr04-03\":\"value-04-03\"n" + + " }\n" + + "}"; + new JSONObject(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr03\" at 90 [character 13 line 5]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key (level 0) holding an array + String str = "{\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\",\n" + +" \"attr03\":\"value-03\",\n" + +" \"attr03\": [\n" + +" {" + +" \"attr04-01\":\"value-04-01\",n" + +" \"attr04-02\":\"value-04-02\",n" + +" \"attr04-03\":\"value-04-03\"n" + +" }\n" + + " ]\n" + + "}"; + new JSONObject(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr03\" at 90 [character 13 line 5]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key (level 1) + String str = "{\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\",\n" + +" \"attr03\":\"value-03\",\n" + +" \"attr04\": {\n" + +" \"attr04-01\":\"value04-01\",\n" + +" \"attr04-02\":\"value04-02\",\n" + +" \"attr04-03\":\"value04-03\",\n" + +" \"attr04-03\":\"value04-04\"\n" + + " }\n" + + "}"; + new JSONObject(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr04-03\" at 215 [character 20 line 9]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key (level 1) holding an object + String str = "{\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\",\n" + +" \"attr03\":\"value-03\",\n" + +" \"attr04\": {\n" + +" \"attr04-01\":\"value04-01\",\n" + +" \"attr04-02\":\"value04-02\",\n" + +" \"attr04-03\":\"value04-03\",\n" + +" \"attr04-03\": {\n" + +" \"attr04-04-01\":\"value04-04-01\",\n" + +" \"attr04-04-02\":\"value04-04-02\",\n" + +" \"attr04-04-03\":\"value04-04-03\",\n" + +" }\n" + +" }\n" + + "}"; + new JSONObject(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr04-03\" at 215 [character 20 line 9]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key (level 1) holding an array + String str = "{\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\",\n" + +" \"attr03\":\"value-03\",\n" + +" \"attr04\": {\n" + +" \"attr04-01\":\"value04-01\",\n" + +" \"attr04-02\":\"value04-02\",\n" + +" \"attr04-03\":\"value04-03\",\n" + +" \"attr04-03\": [\n" + +" {\n" + +" \"attr04-04-01\":\"value04-04-01\",\n" + +" \"attr04-04-02\":\"value04-04-02\",\n" + +" \"attr04-04-03\":\"value04-04-03\",\n" + +" }\n" + +" ]\n" + +" }\n" + + "}"; + new JSONObject(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr04-03\" at 215 [character 20 line 9]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key in object (level 0) within an array + String str = "[\n" + +" {\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\":\"value-02\"\n" + +" },\n" + +" {\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr01\":\"value-02\"\n" + +" }\n" + + "]"; + new JSONArray(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr01\" at 124 [character 17 line 8]", + e.getMessage()); + } + try { + // test exception message when including a duplicate key in object (level 1) within an array + String str = "[\n" + +" {\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\": {\n" + +" \"attr02-01\":\"value-02-01\",\n" + +" \"attr02-02\":\"value-02-02\"\n" + +" }\n" + +" },\n" + +" {\n" + +" \"attr01\":\"value-01\",\n" + +" \"attr02\": {\n" + +" \"attr02-01\":\"value-02-01\",\n" + +" \"attr02-01\":\"value-02-02\"\n" + +" }\n" + +" }\n" + + "]"; + new JSONArray(str); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Expecting an expection message", + "Duplicate key \"attr02-01\" at 269 [character 24 line 13]", + e.getMessage()); + } + } + + /** + * Confirm behavior when putOnce() is called with null parameters + */ + @Test + public void jsonObjectPutOnceNull() { + JSONObject jsonObject = new JSONObject(); + jsonObject.putOnce(null, null); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); + jsonObject.putOnce("", null); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); + jsonObject.putOnce(null, ""); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); + } + + /** + * Exercise JSONObject opt(key, default) method. + */ + @Test + public void jsonObjectOptDefault() { + + String str = "{\"myKey\": \"myval\", \"hiKey\": null}"; + JSONObject jsonObject = new JSONObject(str); + + assertTrue("optBigDecimal() should return default BigDecimal", + BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0); + assertTrue("optBigInteger() should return default BigInteger", + BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0); + assertTrue("optBoolean() should return default boolean", + jsonObject.optBoolean("myKey", true)); + assertTrue("optInt() should return default int", + 42 == jsonObject.optInt("myKey", 42)); + assertTrue("optEnum() should return default Enum", + MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1))); + assertTrue("optJSONArray() should return null ", + null==jsonObject.optJSONArray("myKey")); + assertTrue("optJSONObject() should return null ", + null==jsonObject.optJSONObject("myKey")); + assertTrue("optLong() should return default long", + 42l == jsonObject.optLong("myKey", 42l)); + assertTrue("optDouble() should return default double", + 42.3d == jsonObject.optDouble("myKey", 42.3d)); + assertTrue("optFloat() should return default float", + 42.3f == jsonObject.optFloat("myKey", 42.3f)); + assertTrue("optNumber() should return default Number", + 42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue()); + assertTrue("optString() should return default string", + "hi".equals(jsonObject.optString("hiKey", "hi"))); + } + + /** + * Exercise JSONObject opt(key, default) method when the key doesn't exist. + */ + @Test + public void jsonObjectOptNoKey() { + + JSONObject jsonObject = new JSONObject(); + + assertNull(jsonObject.opt(null)); + + assertTrue("optBigDecimal() should return default BigDecimal", + BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0); + assertTrue("optBigInteger() should return default BigInteger", + BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0); + assertTrue("optBoolean() should return default boolean", + jsonObject.optBoolean("myKey", true)); + assertTrue("optInt() should return default int", + 42 == jsonObject.optInt("myKey", 42)); + assertTrue("optEnum() should return default Enum", + MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1))); + assertTrue("optJSONArray() should return null ", + null==jsonObject.optJSONArray("myKey")); + assertTrue("optJSONObject() should return null ", + null==jsonObject.optJSONObject("myKey")); + assertTrue("optLong() should return default long", + 42l == jsonObject.optLong("myKey", 42l)); + assertTrue("optDouble() should return default double", + 42.3d == jsonObject.optDouble("myKey", 42.3d)); + assertTrue("optFloat() should return default float", + 42.3f == jsonObject.optFloat("myKey", 42.3f)); + assertTrue("optNumber() should return default Number", + 42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue()); + assertTrue("optString() should return default string", + "hi".equals(jsonObject.optString("hiKey", "hi"))); + } + + /** + * Verifies that the opt methods properly convert string values. + */ + @Test + public void jsonObjectOptStringConversion() { + JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}"); + assertTrue("unexpected optBoolean value",jo.optBoolean("true",false)==true); + assertTrue("unexpected optBoolean value",jo.optBoolean("false",true)==false); + assertTrue("unexpected optInt value",jo.optInt("int",0)==123); + assertTrue("unexpected optLong value",jo.optLong("int",0)==123l); + assertTrue("unexpected optDouble value",jo.optDouble("int",0.0d)==123.0d); + assertTrue("unexpected optFloat value",jo.optFloat("int",0.0f)==123.0f); + assertTrue("unexpected optBigInteger value",jo.optBigInteger("int",BigInteger.ZERO).compareTo(new BigInteger("123"))==0); + assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0); + assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0); + assertTrue("unexpected optNumber value",jo.optNumber("int",BigInteger.ZERO).longValue()==123l); + } + + /** + * Verifies that the opt methods properly convert string values to numbers and coerce them consistently. + */ + @Test + public void jsonObjectOptCoercion() { + JSONObject jo = new JSONObject("{\"largeNumberStr\":\"19007199254740993.35481234487103587486413587843213584\"}"); + // currently the parser doesn't recognize BigDecimal, to we have to put it manually + jo.put("largeNumber", new BigDecimal("19007199254740993.35481234487103587486413587843213584")); + + // Test type coercion from larger to smaller + assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumber",null)); + assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumber",null)); + assertEquals(1.9007199254740992E16, jo.optDouble("largeNumber"),0.0); + assertEquals(1.90071995E16f, jo.optFloat("largeNumber"),0.0f); + assertEquals(19007199254740993l, jo.optLong("largeNumber")); + assertEquals(1874919425, jo.optInt("largeNumber")); + + // conversion from a string + assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumberStr",null)); + assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumberStr",null)); + assertEquals(1.9007199254740992E16, jo.optDouble("largeNumberStr"),0.0); + assertEquals(1.90071995E16f, jo.optFloat("largeNumberStr"),0.0f); + assertEquals(19007199254740993l, jo.optLong("largeNumberStr")); + assertEquals(1874919425, jo.optInt("largeNumberStr")); + + // the integer portion of the actual value is larger than a double can hold. + assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumber")); + assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumber")); + assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumberStr")); + assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumberStr")); + assertEquals(19007199254740992l, (long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")); + assertEquals(2147483647, (int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")); + } + + /** + * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently. + */ + @Test + public void jsonObjectOptBigDecimal() { + JSONObject jo = new JSONObject().put("int", 123).put("long", 654L) + .put("float", 1.234f).put("double", 2.345d) + .put("bigInteger", new BigInteger("1234")) + .put("bigDecimal", new BigDecimal("1234.56789")) + .put("nullVal", JSONObject.NULL); + + assertEquals(new BigDecimal("123"),jo.optBigDecimal("int", null)); + assertEquals(new BigDecimal("654"),jo.optBigDecimal("long", null)); + assertEquals(new BigDecimal(1.234f),jo.optBigDecimal("float", null)); + assertEquals(new BigDecimal(2.345d),jo.optBigDecimal("double", null)); + assertEquals(new BigDecimal("1234"),jo.optBigDecimal("bigInteger", null)); + assertEquals(new BigDecimal("1234.56789"),jo.optBigDecimal("bigDecimal", null)); + assertNull(jo.optBigDecimal("nullVal", null)); + assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float")); + assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double")); + } + + /** + * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently. + */ + @Test + public void jsonObjectOptBigInteger() { + JSONObject jo = new JSONObject().put("int", 123).put("long", 654L) + .put("float", 1.234f).put("double", 2.345d) + .put("bigInteger", new BigInteger("1234")) + .put("bigDecimal", new BigDecimal("1234.56789")) + .put("nullVal", JSONObject.NULL); + + assertEquals(new BigInteger("123"),jo.optBigInteger("int", null)); + assertEquals(new BigInteger("654"),jo.optBigInteger("long", null)); + assertEquals(new BigInteger("1"),jo.optBigInteger("float", null)); + assertEquals(new BigInteger("2"),jo.optBigInteger("double", null)); + assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null)); + assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null)); + assertNull(jo.optBigDecimal("nullVal", null)); + } + + /** + * Confirm behavior when JSONObject put(key, null object) is called + */ + @Test + public void jsonObjectputNull() { + + // put null should remove the item. + String str = "{\"myKey\": \"myval\"}"; + JSONObject jsonObjectRemove = new JSONObject(str); + jsonObjectRemove.remove("myKey"); + assertTrue("jsonObject should be empty", jsonObjectRemove.isEmpty()); + + JSONObject jsonObjectPutNull = new JSONObject(str); + jsonObjectPutNull.put("myKey", (Object) null); + assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty()); + + + } + + /** + * Exercise JSONObject quote() method + * This purpose of quote() is to ensure that for strings with embedded + * quotes, the quotes are properly escaped. + */ + @Test + public void jsonObjectQuote() { + String str; + str = ""; + String quotedStr; + quotedStr = JSONObject.quote(str); + assertTrue("quote() expected escaped quotes, found "+quotedStr, + "\"\"".equals(quotedStr)); + str = "\"\""; + quotedStr = JSONObject.quote(str); + assertTrue("quote() expected escaped quotes, found "+quotedStr, + "\"\\\"\\\"\"".equals(quotedStr)); + str = "null and null will be emitted as "" + */ + String sJONull = XML.toString(jsonObjectJONull); + assertTrue("JSONObject.NULL should emit a null value", + "null".equals(sJONull)); + String sNull = XML.toString(jsonObjectNull); + assertTrue("null should emit an empty string", "".equals(sNull)); + } + + @Test(expected = JSONPointerException.class) + public void queryWithNoResult() { + new JSONObject().query("/a/b"); + } + + @Test + public void optQueryWithNoResult() { + assertNull(new JSONObject().optQuery("/a/b")); + } + + @Test(expected = IllegalArgumentException.class) + public void optQueryWithSyntaxError() { + new JSONObject().optQuery("invalid"); + } + + @Test(expected = JSONException.class) + public void invalidEscapeSequence() { + String json = "{ \"\\url\": \"value\" }"; + assertNull("Expected an exception",new JSONObject(json)); + } + + /** + * Exercise JSONObject toMap() method. + */ + @Test + public void toMap() { + String jsonObjectStr = + "{" + + "\"key1\":" + + "[1,2," + + "{\"key3\":true}" + + "]," + + "\"key2\":" + + "{\"key1\":\"val1\",\"key2\":" + + "{\"key2\":null}," + + "\"key3\":42" + + "}," + + "\"key3\":" + + "[" + + "[\"value1\",2.1]" + + "," + + "[null]" + + "]" + + "}"; + + JSONObject jsonObject = new JSONObject(jsonObjectStr); + Map map = jsonObject.toMap(); + + assertTrue("Map should not be null", map != null); + assertTrue("Map should have 3 elements", map.size() == 3); + + List key1List = (List)map.get("key1"); + assertTrue("key1 should not be null", key1List != null); + assertTrue("key1 list should have 3 elements", key1List.size() == 3); + assertTrue("key1 value 1 should be 1", key1List.get(0).equals(Integer.valueOf(1))); + assertTrue("key1 value 2 should be 2", key1List.get(1).equals(Integer.valueOf(2))); + + Map key1Value3Map = (Map)key1List.get(2); + assertTrue("Map should not be null", key1Value3Map != null); + assertTrue("Map should have 1 element", key1Value3Map.size() == 1); + assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE)); + + Map key2Map = (Map)map.get("key2"); + assertTrue("key2 should not be null", key2Map != null); + assertTrue("key2 map should have 3 elements", key2Map.size() == 3); + assertTrue("key2 map key 1 should be val1", key2Map.get("key1").equals("val1")); + assertTrue("key2 map key 3 should be 42", key2Map.get("key3").equals(Integer.valueOf(42))); + + Map key2Val2Map = (Map)key2Map.get("key2"); + assertTrue("key2 map key 2 should not be null", key2Val2Map != null); + assertTrue("key2 map key 2 should have an entry", key2Val2Map.containsKey("key2")); + assertTrue("key2 map key 2 value should be null", key2Val2Map.get("key2") == null); + + List key3List = (List)map.get("key3"); + assertTrue("key3 should not be null", key3List != null); + assertTrue("key3 list should have 3 elements", key3List.size() == 2); + + List key3Val1List = (List)key3List.get(0); + assertTrue("key3 list val 1 should not be null", key3Val1List != null); + assertTrue("key3 list val 1 should have 2 elements", key3Val1List.size() == 2); + assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1")); + assertTrue("key3 list val 1 list element 2 should be 2.1", key3Val1List.get(1).equals(Double.valueOf("2.1"))); + + List key3Val2List = (List)key3List.get(1); + assertTrue("key3 list val 2 should not be null", key3Val2List != null); + assertTrue("key3 list val 2 should have 1 element", key3Val2List.size() == 1); + assertTrue("key3 list val 2 list element 1 should be null", key3Val2List.get(0) == null); + + // Assert that toMap() is a deep copy + jsonObject.getJSONArray("key3").getJSONArray(0).put(0, "still value 1"); + assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1")); + + // assert that the new map is mutable + assertTrue("Removing a key should succeed", map.remove("key3") != null); + assertTrue("Map should have 2 elements", map.size() == 2); + } + + /** + * test that validates a singleton can be serialized as a bean. + */ + @Test + public void testSingletonBean() { + final JSONObject jo = new JSONObject(Singleton.getInstance()); + assertEquals(jo.keySet().toString(), 1, jo.length()); + assertEquals(0, jo.get("someInt")); + assertEquals(null, jo.opt("someString")); + + // Update the singleton values + Singleton.getInstance().setSomeInt(42); + Singleton.getInstance().setSomeString("Something"); + final JSONObject jo2 = new JSONObject(Singleton.getInstance()); + assertEquals(2, jo2.length()); + assertEquals(42, jo2.get("someInt")); + assertEquals("Something", jo2.get("someString")); + + // ensure our original jo hasn't changed. + assertEquals(0, jo.get("someInt")); + assertEquals(null, jo.opt("someString")); + } + + /** + * test that validates a singleton can be serialized as a bean. + */ + @Test + public void testSingletonEnumBean() { + final JSONObject jo = new JSONObject(SingletonEnum.getInstance()); + assertEquals(jo.keySet().toString(), 1, jo.length()); + assertEquals(0, jo.get("someInt")); + assertEquals(null, jo.opt("someString")); + + // Update the singleton values + SingletonEnum.getInstance().setSomeInt(42); + SingletonEnum.getInstance().setSomeString("Something"); + final JSONObject jo2 = new JSONObject(SingletonEnum.getInstance()); + assertEquals(2, jo2.length()); + assertEquals(42, jo2.get("someInt")); + assertEquals("Something", jo2.get("someString")); + + // ensure our original jo hasn't changed. + assertEquals(0, jo.get("someInt")); + assertEquals(null, jo.opt("someString")); + } + + /** + * Test to validate that a generic class can be serialized as a bean. + */ + @Test + public void testGenericBean() { + GenericBean bean = new GenericBean<>(42); + final JSONObject jo = new JSONObject(bean); + assertEquals(jo.keySet().toString(), 8, jo.length()); + assertEquals(42, jo.get("genericValue")); + assertEquals("Expected the getter to only be called once", + 1, bean.genericGetCounter); + assertEquals(0, bean.genericSetCounter); + } + + /** + * Test to validate that a generic class can be serialized as a bean. + */ + @Test + public void testGenericIntBean() { + GenericBeanInt bean = new GenericBeanInt(42); + final JSONObject jo = new JSONObject(bean); + assertEquals(jo.keySet().toString(), 10, jo.length()); + assertEquals(42, jo.get("genericValue")); + assertEquals("Expected the getter to only be called once", + 1, bean.genericGetCounter); + assertEquals(0, bean.genericSetCounter); + } + + /** + * Test to verify key limitations in the JSONObject bean serializer. + */ + @Test + public void testWierdListBean() { + WeirdList bean = new WeirdList(42, 43, 44); + final JSONObject jo = new JSONObject(bean); + // get() should have a key of 0 length + // get(int) should be ignored base on parameter count + // getInt(int) should also be ignored based on parameter count + // add(Integer) should be ignore as it doesn't start with get/is and also has a parameter + // getALL should be mapped + assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(), + 1, jo.length()); + assertNotNull(jo.get("ALL")); + } + + /** + * Tests the exception portions of populateMap. + */ + @Test + public void testExceptionalBean() { + ExceptionalBean bean = new ExceptionalBean(); + final JSONObject jo = new JSONObject(bean); + assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(), + 1, jo.length()); + assertTrue(jo.get("closeable") instanceof JSONObject); + assertTrue(jo.getJSONObject("closeable").has("string")); + } + + @Test(expected=NullPointerException.class) + public void testPutNullBoolean() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, false); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullCollection() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, Collections.emptySet()); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullDouble() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, 0.0d); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullFloat() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, 0.0f); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullInt() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, 0); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullLong() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, 0L); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullMap() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, Collections.emptyMap()); + fail("Expected an exception"); + } + @Test(expected=NullPointerException.class) + public void testPutNullObject() { + // null put key + JSONObject jsonObject = new JSONObject("{}"); + jsonObject.put(null, new Object()); + fail("Expected an exception"); + } + +} diff --git a/src/test/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java similarity index 50% rename from src/test/org/json/junit/JSONPointerTest.java rename to src/test/java/org/json/junit/JSONPointerTest.java index 95fa73b..5ddd089 100644 --- a/src/test/org/json/junit/JSONPointerTest.java +++ b/src/test/java/org/json/junit/JSONPointerTest.java @@ -1,8 +1,18 @@ package org.json.junit; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import org.json.*; +import java.io.IOException; +import java.io.InputStream; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONPointer; +import org.json.JSONPointerException; +import org.json.JSONTokener; import org.junit.Test; public class JSONPointerTest { @@ -10,8 +20,12 @@ public class JSONPointerTest { private static final JSONObject document; static { - document = new JSONObject(new JSONTokener( - JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json"))); + @SuppressWarnings("resource") + InputStream resourceAsStream = JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json"); + if(resourceAsStream == null) { + throw new ExceptionInInitializerError("Unable to locate test file. Please check your development environment configuration"); + } + document = new JSONObject(new JSONTokener(resourceAsStream)); } private Object query(String pointer) { @@ -48,6 +62,27 @@ public void queryByEmptyKey() { assertSame(document.get(""), query("/")); } + @Test + public void queryByEmptyKeySubObject() { + assertSame(document.getJSONObject("obj").getJSONObject(""), query("/obj/")); + } + + @Test + public void queryByEmptyKeySubObjectSubOject() { + assertSame( + document.getJSONObject("obj").getJSONObject("").get(""), + query("/obj//") + ); + } + + @Test + public void queryByEmptyKeySubObjectValue() { + assertSame( + document.getJSONObject("obj").getJSONObject("").get("subKey"), + query("/obj//subKey") + ); + } + @Test public void slashEscaping() { assertSame(document.get("a/b"), query("/a~1b")); @@ -78,6 +113,11 @@ public void uriFragmentNotation() { assertSame(document.get("foo"), query("#/foo")); } + @Test + public void uriFragmentNotationRoot() { + assertSame(document, query("#")); + } + @Test public void uriFragmentPercentHandling() { assertSame(document.get("c%d"), query("#/c%25d")); @@ -152,7 +192,7 @@ public void tokenListIsCopiedInConstructor() { } /** - * Coverage for JSONObject queryFrom() + * Coverage for JSONObject query(String) */ @Test public void queryFromJSONObject() { @@ -182,7 +222,61 @@ public void queryFromJSONObject() { } /** - * Coverage for JSONArray queryFrom() + * Coverage for JSONObject query(JSONPointer) + */ + @Test + public void queryFromJSONObjectUsingPointer() { + String str = "{"+ + "\"stringKey\":\"hello world!\","+ + "\"arrayKey\":[0,1,2],"+ + "\"objectKey\": {"+ + "\"a\":\"aVal\","+ + "\"b\":\"bVal\""+ + "}"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + Object obj = jsonObject.query(new JSONPointer("/stringKey")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonObject.query(new JSONPointer("/arrayKey/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonObject.query(new JSONPointer("/objectKey/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + try { + obj = jsonObject.query(new JSONPointer("/a/b/c")); + assertTrue("Expected JSONPointerException", false); + } catch (JSONPointerException e) { + assertTrue("Expected bad key/value exception", + "value [null] is not an array or object therefore its key b cannot be resolved". + equals(e.getMessage())); + } + } + + /** + * Coverage for JSONObject optQuery(JSONPointer) + */ + @Test + public void optQueryFromJSONObjectUsingPointer() { + String str = "{"+ + "\"stringKey\":\"hello world!\","+ + "\"arrayKey\":[0,1,2],"+ + "\"objectKey\": {"+ + "\"a\":\"aVal\","+ + "\"b\":\"bVal\""+ + "}"+ + "}"; + JSONObject jsonObject = new JSONObject(str); + Object obj = jsonObject.optQuery(new JSONPointer("/stringKey")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonObject.optQuery(new JSONPointer("/arrayKey/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonObject.optQuery(new JSONPointer("/objectKey/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + obj = jsonObject.optQuery(new JSONPointer("/a/b/c")); + assertTrue("Expected null", obj == null); + } + + /** + * Coverage for JSONArray query(String) */ @Test public void queryFromJSONArray() { @@ -209,4 +303,57 @@ public void queryFromJSONArray() { "a is not an array index".equals(e.getMessage())); } } + + /** + * Coverage for JSONArray query(JSONPointer) + */ + @Test + public void queryFromJSONArrayUsingPointer() { + String str = "["+ + "\"hello world!\","+ + "[0,1,2],"+ + "{"+ + "\"a\":\"aVal\","+ + "\"b\":\"bVal\""+ + "}"+ + "]"; + JSONArray jsonArray = new JSONArray(str); + Object obj = jsonArray.query(new JSONPointer("/0")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonArray.query(new JSONPointer("/1/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonArray.query(new JSONPointer("/2/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + try { + obj = jsonArray.query(new JSONPointer("/a/b/c")); + assertTrue("Expected JSONPointerException", false); + } catch (JSONPointerException e) { + assertTrue("Expected bad index exception", + "a is not an array index".equals(e.getMessage())); + } + } + + /** + * Coverage for JSONArray optQuery(JSONPointer) + */ + @Test + public void optQueryFromJSONArrayUsingPointer() { + String str = "["+ + "\"hello world!\","+ + "[0,1,2],"+ + "{"+ + "\"a\":\"aVal\","+ + "\"b\":\"bVal\""+ + "}"+ + "]"; + JSONArray jsonArray = new JSONArray(str); + Object obj = jsonArray.optQuery(new JSONPointer("/0")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonArray.optQuery(new JSONPointer("/1/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonArray.optQuery(new JSONPointer("/2/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + obj = jsonArray.optQuery(new JSONPointer("/a/b/c")); + assertTrue("Expected null", obj == null); + } } diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java new file mode 100644 index 0000000..ec40dbb --- /dev/null +++ b/src/test/java/org/json/junit/JSONStringTest.java @@ -0,0 +1,371 @@ +package org.json.junit; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.*; + +import org.json.*; +import org.junit.Test; + +/** + * Tests for JSONString implementations, and the difference between + * {@link JSONObject#valueToString} and {@link JSONObject#writeValue}. + */ +public class JSONStringTest { + + /** + * This tests the JSONObject.writeValue() method. We can't test directly + * due to it being a package-protected method. Instead, we can call + * JSONArray.write(), which delegates the writing of each entry to + * writeValue(). + */ + @Test + public void writeValues() throws Exception { + JSONArray jsonArray = new JSONArray(); + jsonArray.put((Object)null); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[null]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(JSONObject.NULL); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[null]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(new JSONObject()); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[{}]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(new JSONArray()); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[[]]".equals(output)); + + jsonArray = new JSONArray(); + Map singleMap = Collections.singletonMap("key1", "value1"); + jsonArray.put((Object)singleMap); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[{\"key1\":\"value1\"}]".equals(output)); + + jsonArray = new JSONArray(); + List singleList = Collections.singletonList("entry1"); + jsonArray.put((Object)singleList); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[[\"entry1\"]]".equals(output)); + + jsonArray = new JSONArray(); + int[] intArray = new int[] { 1, 2, 3 }; + jsonArray.put(intArray); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[[1,2,3]]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(24); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[24]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put("string value"); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"string value\"]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(true); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[true]".equals(output)); + } finally { + writer.close(); + } + + } + + /** + * This tests the JSONObject.valueToString() method. These should be + * identical to the values above, except for the enclosing [ and ]. + */ + @SuppressWarnings("boxing") + @Test + public void valuesToString() throws Exception { + + String output = JSONObject.valueToString(null); + assertTrue("String values should be equal", "null".equals(output)); + + output = JSONObject.valueToString(JSONObject.NULL); + assertTrue("String values should be equal", "null".equals(output)); + + output = JSONObject.valueToString(new JSONObject()); + assertTrue("String values should be equal", "{}".equals(output)); + + output = JSONObject.valueToString(new JSONArray()); + assertTrue("String values should be equal", "[]".equals(output)); + + Map singleMap = Collections.singletonMap("key1", "value1"); + output = JSONObject.valueToString(singleMap); + assertTrue("String values should be equal", "{\"key1\":\"value1\"}".equals(output)); + + List singleList = Collections.singletonList("entry1"); + output = JSONObject.valueToString(singleList); + assertTrue("String values should be equal", "[\"entry1\"]".equals(output)); + + int[] intArray = new int[] { 1, 2, 3 }; + output = JSONObject.valueToString(intArray); + assertTrue("String values should be equal", "[1,2,3]".equals(output)); + + output = JSONObject.valueToString(24); + assertTrue("String values should be equal", "24".equals(output)); + + output = JSONObject.valueToString("string value"); + assertTrue("String values should be equal", "\"string value\"".equals(output)); + + output = JSONObject.valueToString(true); + assertTrue("String values should be equal", "true".equals(output)); + + } + + /** + * Test what happens when toJSONString() returns a well-formed JSON value. + * This is the usual case. + */ + @Test + public void testJSONStringValue() throws Exception { + JSONStringValue jsonString = new JSONStringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(jsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"the JSON string value\"]".equals(output)); + + output = JSONObject.valueToString(jsonString); + assertTrue("String values should be equal", "\"the JSON string value\"".equals(output)); + } finally { + writer.close(); + } + } + + /** + * Test what happens when toJSONString() returns null. In one case, + * use the object's toString() method. In the other, throw a JSONException. + */ + @Test + public void testJSONNullStringValue() throws Exception { + JSONNullStringValue jsonString = new JSONNullStringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(jsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"the toString value\"]".equals(output)); + + // The only different between writeValue() and valueToString(): + // in this case, valueToString throws a JSONException + try { + output = JSONObject.valueToString(jsonString); + fail("Expected an exception, got a String value"); + } catch (Exception e) { + assertTrue("Expected JSONException", e instanceof JSONException); + assertTrue("Exception message does not match", "Bad value from toJSONString: null".equals(e.getMessage())); + } + } finally { + writer.close(); + } + } + + /** + * Test what happens when toJSONString() returns an exception. In both + * cases, a JSONException is thrown, with the cause and message set from + * the original exception. + */ + @Test + public void testJSONStringExceptionValue() throws IOException { + JSONStringExceptionValue jsonString = new JSONStringExceptionValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(jsonString); + + StringWriter writer = new StringWriter(); + try { + jsonArray.write(writer).toString(); + fail("Expected an exception, got a String value"); + } catch (JSONException e) { + assertEquals("Unable to write JSONArray value at index: 0", e.getMessage()); + } catch(Exception e) { + fail("Expected JSONException"); + } finally { + writer.close(); + } + + try { + JSONObject.valueToString(jsonString); + fail("Expected an exception, got a String value"); + } catch (JSONException e) { + assertTrue("Exception message does not match", "the exception value".equals(e.getMessage())); + } catch(Exception e) { + fail("Expected JSONException"); + } + } + + /** + * Test what happens when a Java object's toString() returns a String value. + * This is the usual case. + */ + @Test + public void testStringValue() throws Exception { + StringValue nonJsonString = new StringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(nonJsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"the toString value for StringValue\"]".equals(output)); + + output = JSONObject.valueToString(nonJsonString); + assertTrue("String values should be equal", "\"the toString value for StringValue\"".equals(output)); + } finally { + writer.close(); + } + } + + /** + * Test what happens when a Java object's toString() returns null. + * Defaults to empty string. + */ + @Test + public void testNullStringValue() throws Exception { + NullStringValue nonJsonString = new NullStringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(nonJsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"\"]".equals(output)); + + output = JSONObject.valueToString(nonJsonString); + assertTrue("String values should be equal", "\"\"".equals(output)); + } finally { + writer.close(); + } + } + + /** + * A JSONString that returns a valid JSON string value. + */ + private static final class JSONStringValue implements JSONString { + + @Override + public String toJSONString() { + return "\"the JSON string value\""; + } + + @Override + public String toString() { + return "the toString value for JSONStringValue"; + } + } + + /** + * A JSONString that returns null when calling toJSONString(). + */ + private static final class JSONNullStringValue implements JSONString { + + @Override + public String toJSONString() { + return null; + } + + @Override + public String toString() { + return "the toString value"; + } + } + + /** + * A JSONString that throw an exception when calling toJSONString(). + */ + private static final class JSONStringExceptionValue implements JSONString { + + @Override + public String toJSONString() { + throw new IllegalStateException("the exception value"); + } + + @Override + public String toString() { + return "the toString value for JSONStringExceptionValue"; + } + } + + public static final class StringValue { + + @Override + public String toString() { + return "the toString value for StringValue"; + } + } + + public static final class NullStringValue { + + @Override + public String toString() { + return null; + } + } +} diff --git a/src/test/org/json/junit/JSONStringerTest.java b/src/test/java/org/json/junit/JSONStringerTest.java similarity index 74% rename from src/test/org/json/junit/JSONStringerTest.java rename to src/test/java/org/json/junit/JSONStringerTest.java index 19b46de..99cdd6f 100644 --- a/src/test/org/json/junit/JSONStringerTest.java +++ b/src/test/java/org/json/junit/JSONStringerTest.java @@ -27,7 +27,7 @@ public void nullKeyException() { jsonStringer.key(null); assertTrue("Expected an exception", false); } catch (JSONException e) { - assertTrue("Expected an exception message", + assertTrue("Expected an exception message", "Null key.". equals(e.getMessage())); } @@ -44,7 +44,7 @@ public void outOfSequenceException() { jsonStringer.key("hi"); assertTrue("Expected an exception", false); } catch (JSONException e) { - assertTrue("Expected an exception message", + assertTrue("Expected an exception message", "Misplaced key.". equals(e.getMessage())); } @@ -60,8 +60,9 @@ public void missplacedArrayException() { jsonStringer.object().endObject(); try { jsonStringer.array(); + assertTrue("Expected an exception", false); } catch (JSONException e) { - assertTrue("Expected an exception message", + assertTrue("Expected an exception message", "Misplaced array.". equals(e.getMessage())); } @@ -77,8 +78,9 @@ public void missplacedEndArrayException() { jsonStringer.object(); try { jsonStringer.endArray(); + assertTrue("Expected an exception", false); } catch (JSONException e) { - assertTrue("Expected an exception message", + assertTrue("Expected an exception message", "Misplaced endArray.". equals(e.getMessage())); } @@ -94,8 +96,9 @@ public void missplacedEndObjectException() { jsonStringer.array(); try { jsonStringer.endObject(); + assertTrue("Expected an exception", false); } catch (JSONException e) { - assertTrue("Expected an exception message", + assertTrue("Expected an exception message", "Misplaced endObject.". equals(e.getMessage())); } @@ -111,8 +114,9 @@ public void missplacedObjectException() { jsonStringer.object().endObject(); try { jsonStringer.object(); + assertTrue("Expected an exception", false); } catch (JSONException e) { - assertTrue("Expected an exception message", + assertTrue("Expected an exception message", "Misplaced object.". equals(e.getMessage())); } @@ -125,7 +129,47 @@ public void missplacedObjectException() { @Test public void exceedNestDepthException() { try { - new JSONStringer().object(). + JSONStringer s = new JSONStringer(); + s.object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). + key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(); + s.key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). @@ -165,9 +209,10 @@ public void exceedNestDepthException() { key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(). key("k").object().key("k").object().key("k").object().key("k").object().key("k").object(); + fail("Expected an exception message"); } catch (JSONException e) { - assertTrue("Expected an exception message", - "". + assertTrue("Expected an exception message", + "Nesting too deep.". equals(e.getMessage())); } } diff --git a/src/test/java/org/json/junit/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java new file mode 100644 index 0000000..de1564d --- /dev/null +++ b/src/test/java/org/json/junit/JSONTokenerTest.java @@ -0,0 +1,295 @@ +package org.json.junit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.junit.Test; + +/** + * Test specific to the {@link org.json.JSONTokener} class. + * @author John Aylward + * + */ +public class JSONTokenerTest { + + /** + * verify that back() fails as expected. + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void verifyBackFailureZeroIndex() throws IOException { + try(Reader reader = new StringReader("some test string")) { + final JSONTokener tokener = new JSONTokener(reader); + try { + // this should fail since the index is 0; + tokener.back(); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Stepping back two steps is not supported", e.getMessage()); + } catch (Exception e) { + fail("Unknown Exception type " + e.getClass().getCanonicalName()+" with message "+e.getMessage()); + } + + } + } + /** + * verify that back() fails as expected. + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void verifyBackFailureDoubleBack() throws IOException { + try(Reader reader = new StringReader("some test string")) { + final JSONTokener tokener = new JSONTokener(reader); + tokener.next(); + tokener.back(); + try { + // this should fail since the index is 0; + tokener.back(); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Stepping back two steps is not supported", e.getMessage()); + } catch (Exception e) { + fail("Unknown Exception type " + e.getClass().getCanonicalName()+" with message "+e.getMessage()); + } + } + } + + @Test + public void testValid() { + checkValid("0",Number.class); + checkValid(" 0 ",Number.class); + checkValid("23",Number.class); + checkValid("23.5",Number.class); + checkValid(" 23.5 ",Number.class); + checkValid("null",null); + checkValid(" null ",null); + checkValid("true",Boolean.class); + checkValid(" true\n",Boolean.class); + checkValid("false",Boolean.class); + checkValid("\nfalse ",Boolean.class); + checkValid("{}",JSONObject.class); + checkValid(" {} ",JSONObject.class); + checkValid("{\"a\":1}",JSONObject.class); + checkValid(" {\"a\":1} ",JSONObject.class); + checkValid("[]",JSONArray.class); + checkValid(" [] ",JSONArray.class); + checkValid("[1,2]",JSONArray.class); + checkValid("\n\n[1,2]\n\n",JSONArray.class); + checkValid("1 2", String.class); + } + + @Test + public void testErrors() { + // Check that stream can detect that a value is found after + // the first one + checkError(" { \"a\":1 } 4 "); + checkError("null \"a\""); + checkError("{} true"); + } + + private Object checkValid(String testStr, Class aClass) { + Object result = nextValue(testStr); + + // Check class of object returned + if( null == aClass ) { + if(JSONObject.NULL.equals(result)) { + // OK + } else { + throw new JSONException("Unexpected class: "+result.getClass().getSimpleName()); + } + } else { + if( null == result ) { + throw new JSONException("Unexpected null result"); + } else if(!aClass.isAssignableFrom(result.getClass()) ) { + throw new JSONException("Unexpected class: "+result.getClass().getSimpleName()); + } + } + + return result; + } + + private void checkError(String testStr) { + try { + nextValue(testStr); + + fail("Error should be triggered: (\""+testStr+"\")"); + } catch (JSONException e) { + // OK + } + } + + /** + * Verifies that JSONTokener can read a stream that contains a value. After + * the reading is done, check that the stream is left in the correct state + * by reading the characters after. All valid cases should reach end of stream. + * @param testStr + * @return + * @throws Exception + */ + private Object nextValue(String testStr) throws JSONException { + try(StringReader sr = new StringReader(testStr);){ + JSONTokener tokener = new JSONTokener(sr); + + Object result = tokener.nextValue(); + + if( result == null ) { + throw new JSONException("Unable to find value token in JSON stream: ("+tokener+"): "+testStr); + } + + char c = tokener.nextClean(); + if( 0 != c ) { + throw new JSONException("Unexpected character found at end of JSON stream: "+c+ " ("+tokener+"): "+testStr); + } + + return result; + } + + } + + /** + * Tests the failure of the skipTo method with a buffered reader. Preferably + * we'd like this not to fail but at this time we don't have a good recovery. + * + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void testSkipToFailureWithBufferedReader() throws IOException { + final byte[] superLongBuffer = new byte[1000001]; + // fill our buffer + for(int i=0;i\n"+ + "\n"+ + "
\n"+ + " \n"+ + " abc street\n"+ + "
\n"+ + "
"; + try { + XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS); + fail("Expecting a JSONException"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Misshaped tag at 176 [character 14 line 4]", + e.getMessage()); + } + } + + /** + * Invalid XML string ('!' char in tag) + * Expects a JSONException + */ + @Test + public void shouldHandleInvalidBangInTag() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " \n"+ + " \n"+ + "
\n"+ + "
"; + try { + XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS); + fail("Expecting a JSONException"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Misshaped meta tag at 214 [character 12 line 7]", + e.getMessage()); + } + } + + /** + * Invalid XML string ('!' char and no closing tag brace) + * Expects a JSONException + */ + @Test + public void shouldHandleInvalidBangNoCloseInTag() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " \n"+ + " \n"+ + ""; + try { + XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS); + fail("Expecting a JSONException"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Misshaped meta tag at 213 [character 12 line 7]", + e.getMessage()); + } + } + + /** + * Invalid XML string (no end brace for tag) + * Expects JSONException + */ + @Test + public void shouldHandleNoCloseStartTag() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " \n"+ + " \n"+ + ""; + try { + XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS); + fail("Expecting a JSONException"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Misplaced '<' at 193 [character 4 line 6]", + e.getMessage()); + } + } + + /** + * Invalid XML string (partial CDATA chars in tag name) + * Expects JSONException + */ + @Test + public void shouldHandleInvalidCDATABangInTag() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " Joe Tester\n"+ + " \n"+ + "
\n"+ + "
"; + try { + XMLParserConfiguration config = + new XMLParserConfiguration("altContent"); + XML.toJSONObject(xmlStr, config); + fail("Expecting a JSONException"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Expected 'CDATA[' at 204 [character 11 line 5]", + e.getMessage()); + } + } + + /** + * Null JSONObject in XML.toString() + */ + @Test + public void shouldHandleNullJSONXML() { + JSONObject jsonObject= null; + String actualXml = XML.toString(jsonObject, null, + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("generated XML does not equal expected XML","\"null\"",actualXml); + } + + /** + * Empty JSONObject in XML.toString() + */ + @Test + public void shouldHandleEmptyJSONXML() { + JSONObject jsonObject= new JSONObject(); + String xmlStr = XML.toString(jsonObject, null, + XMLParserConfiguration.KEEP_STRINGS); + assertTrue("xml string should be empty", xmlStr.isEmpty()); + } + + /** + * No SML start tag. The ending tag ends up being treated as content. + */ + @Test + public void shouldHandleNoStartTag() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " \n"+ + " >\n"+ + "
\n"+ + "
"; + String expectedStr = + "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ + "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + JSONObject jsonObject = XML.toJSONObject(xmlStr, + XMLParserConfiguration.KEEP_STRINGS); + JSONObject expectedJsonObject = new JSONObject(expectedStr); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + } + + /** + * Valid XML to JSONObject + */ + @Test + public void shouldHandleSimpleXML() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " Joe Tester\n"+ + " [CDATA[Baker street 5]\n"+ + " \n"+ + " true\n"+ + " false\n"+ + " null\n"+ + " 42\n"+ + " -23\n"+ + " -23.45\n"+ + " -23x.45\n"+ + " 1, 2, 3, 4.1, 5.2\n"+ + "
\n"+ + "
"; + + String expectedStr = + "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+ + "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+ + "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+ + "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+ + "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ + "},\"xsi:noNamespaceSchemaLocation\":"+ + "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ + "XMLSchema-instance\"}}"; + + XMLParserConfiguration config = + new XMLParserConfiguration("altContent"); + compareStringToJSONObject(xmlStr, expectedStr, config); + compareReaderToJSONObject(xmlStr, expectedStr, config); + compareFileToJSONObject(xmlStr, expectedStr); + } + + /** + * Valid XML with comments to JSONObject + */ + @Test + public void shouldHandleCommentsInXML() { + + String xmlStr = + "\n"+ + "\n"+ + "\n"+ + "
\n"+ + " comment ]]>\n"+ + " Joe Tester\n"+ + " \n"+ + " Baker street 5\n"+ + "
\n"+ + "
"; + XMLParserConfiguration config = + new XMLParserConfiguration("altContent"); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+ + "street 5\",\"name\":\"Joe Tester\",\"altContent\":\" this is -- "+ + " comment \"}}}"; + JSONObject expectedJsonObject = new JSONObject(expectedStr); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + } + + /** + * Valid XML to XML.toString() + */ + @Test + public void shouldHandleToString() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " [CDATA[Joe & T > e < s " t ' er]]\n"+ + " Baker street 5\n"+ + " 1, 2, 3, 4.1, 5.2\n"+ + "
\n"+ + "
"; + + String expectedStr = + "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\","+ + "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\","+ + "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ + "},\"xsi:noNamespaceSchemaLocation\":"+ + "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ + "XMLSchema-instance\"}}"; + + JSONObject jsonObject = XML.toJSONObject(xmlStr, + XMLParserConfiguration.KEEP_STRINGS); + String xmlToStr = XML.toString(jsonObject, null, + XMLParserConfiguration.KEEP_STRINGS); + JSONObject finalJsonObject = XML.toJSONObject(xmlToStr, + XMLParserConfiguration.KEEP_STRINGS); + JSONObject expectedJsonObject = new JSONObject(expectedStr); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); + } + + /** + * Converting a JSON doc containing '>' content to JSONObject, then + * XML.toString() should result in valid XML. + */ + @Test + public void shouldHandleContentNoArraytoString() { + String expectedStr = + "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ + "altContent\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + JSONObject expectedJsonObject = new JSONObject(expectedStr); + XMLParserConfiguration config = new XMLParserConfiguration("altContent"); + String finalStr = XML.toString(expectedJsonObject, null, config); + String expectedFinalStr = "
>"+ + "
test.xsdhttp://www.w3.org/2001/XMLSche"+ + "ma-instance
"; + assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+ + finalStr+"]", expectedFinalStr.equals(finalStr)); + } + + /** + * Converting a JSON doc containing a 'content' array to JSONObject, then + * XML.toString() should result in valid XML. + * TODO: This is probably an error in how the 'content' keyword is used. + */ + @Test + public void shouldHandleContentArraytoString() { + String expectedStr = + "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ + "altContent\":[1, 2, 3]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + JSONObject expectedJsonObject = new JSONObject(expectedStr); + XMLParserConfiguration config = new XMLParserConfiguration("altContent"); + String finalStr = XML.toString(expectedJsonObject, null, config); + String expectedFinalStr = "
"+ + "1\n2\n3"+ + "
test.xsdhttp://www.w3.org/2001/XMLSche"+ + "ma-instance
"; + assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+ + finalStr+"]", expectedFinalStr.equals(finalStr)); + } + + /** + * Converting a JSON doc containing a named array to JSONObject, then + * XML.toString() should result in valid XML. + */ + @Test + public void shouldHandleArraytoString() { + String expectedStr = + "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+ + "\"something\":[1, 2, 3]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + JSONObject expectedJsonObject = new JSONObject(expectedStr); + String finalStr = XML.toString(expectedJsonObject, null, + XMLParserConfiguration.KEEP_STRINGS); + String expectedFinalStr = "
"+ + "123"+ + "
test.xsdhttp://www.w3.org/2001/XMLSche"+ + "ma-instance
"; + assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+ + finalStr+"]", expectedFinalStr.equals(finalStr)); + } + + /** + * Tests that the XML output for empty arrays is consistent. + */ + @Test + public void shouldHandleEmptyArray(){ + final JSONObject jo1 = new JSONObject(); + jo1.put("array",new Object[]{}); + final JSONObject jo2 = new JSONObject(); + jo2.put("array",new JSONArray()); + + final String expected = ""; + String output1 = XML.toString(jo1, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected an empty root tag", expected, output1); + String output2 = XML.toString(jo2, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected an empty root tag", expected, output2); + } + + /** + * Tests that the XML output for arrays is consistent when an internal array is empty. + */ + @Test + public void shouldHandleEmptyMultiArray(){ + final JSONObject jo1 = new JSONObject(); + jo1.put("arr",new Object[]{"One", new String[]{}, "Four"}); + final JSONObject jo2 = new JSONObject(); + jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{}), "Four"})); + + final String expected = "OneFour"; + String output1 = XML.toString(jo1, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected a matching array", expected, output1); + String output2 = XML.toString(jo2, "jo", + XMLParserConfiguration.KEEP_STRINGS); + + assertEquals("Expected a matching array", expected, output2); + } + + /** + * Tests that the XML output for arrays is consistent when arrays are not empty. + */ + @Test + public void shouldHandleNonEmptyArray(){ + final JSONObject jo1 = new JSONObject(); + jo1.put("arr",new String[]{"One", "Two", "Three"}); + final JSONObject jo2 = new JSONObject(); + jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"})); + + final String expected = "OneTwoThree"; + String output1 = XML.toString(jo1, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected a non empty root tag", expected, output1); + String output2 = XML.toString(jo2, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected a non empty root tag", expected, output2); + } + + /** + * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays. + */ + @Test + public void shouldHandleMultiArray(){ + final JSONObject jo1 = new JSONObject(); + jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"}); + final JSONObject jo2 = new JSONObject(); + jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{"Two", "Three"}), "Four"})); + + final String expected = "OneTwoThreeFour"; + String output1 = XML.toString(jo1, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected a matching array", expected, output1); + String output2 = XML.toString(jo2, "jo", + XMLParserConfiguration.KEEP_STRINGS); + assertEquals("Expected a matching array", expected, output2); + } + + /** + * Converting a JSON doc containing a named array of nested arrays to + * JSONObject, then XML.toString() should result in valid XML. + */ + @Test + public void shouldHandleNestedArraytoString() { + String xmlStr = + "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+ + "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ + "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; + JSONObject jsonObject = new JSONObject(xmlStr); + String finalStr = XML.toString(jsonObject, null, + XMLParserConfiguration.ORIGINAL); + JSONObject finalJsonObject = XML.toJSONObject(finalStr); + String expectedStr = "
"+ + "12"+ + "3"+ + "
test.xsdhttp://www.w3.org/2001/XMLSche"+ + "ma-instance
"; + JSONObject expectedJsonObject = XML.toJSONObject(expectedStr, + XMLParserConfiguration.ORIGINAL); + Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); + } + + + /** + * Possible bug: + * Illegal node-names must be converted to legal XML-node-names. + * The given example shows 2 nodes which are valid for JSON, but not for XML. + * Therefore illegal arguments should be converted to e.g. an underscore (_). + */ + @Test + public void shouldHandleIllegalJSONNodeNames() + { + JSONObject inputJSON = new JSONObject(); + inputJSON.append("123IllegalNode", "someValue1"); + inputJSON.append("Illegal@node", "someValue2"); + + String result = XML.toString(inputJSON, null, + XMLParserConfiguration.KEEP_STRINGS); + + /* + * This is invalid XML. Names should not begin with digits or contain + * certain values, including '@'. One possible solution is to replace + * illegal chars with '_', in which case the expected output would be: + * <___IllegalNode>someValue1someValue2 + */ + String expected = "<123IllegalNode>someValue1someValue2"; + + assertEquals(expected, result); + } + + /** + * JSONObject with NULL value, to XML.toString() + */ + @Test + public void shouldHandleNullNodeValue() + { + JSONObject inputJSON = new JSONObject(); + inputJSON.put("nullValue", JSONObject.NULL); + // This is a possible preferred result + // String expectedXML = ""; + /** + * This is the current behavior. JSONObject.NULL is emitted as + * the string, "null". + */ + String actualXML = "null"; + String resultXML = XML.toString(inputJSON, null, + XMLParserConfiguration.KEEP_STRINGS); + assertEquals(actualXML, resultXML); + } + + /** + * Investigate exactly how the "content" keyword works + */ + @Test + public void contentOperations() { + /* + * When a standalone 0) then return]]>"; + JSONObject jsonObject = XML.toJSONObject(xmlStr, + XMLParserConfiguration.KEEP_STRINGS); + assertTrue("1. 3 items", 3 == jsonObject.length()); + assertTrue("1. empty tag1", "".equals(jsonObject.get("tag1"))); + assertTrue("1. empty tag2", "".equals(jsonObject.get("tag2"))); + assertTrue("1. content found", "if (a < b && a > 0) then return".equals(jsonObject.get("content"))); + + // multiple consecutive standalone cdatas are accumulated into an array + xmlStr = " 0) then return]]>"; + jsonObject = XML.toJSONObject(xmlStr, + new XMLParserConfiguration(true, "altContent")); + assertTrue("2. 3 items", 3 == jsonObject.length()); + assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1"))); + assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2"))); + assertTrue("2. content array found", jsonObject.get("altContent") instanceof JSONArray); + JSONArray jsonArray = jsonObject.getJSONArray("altContent"); + assertTrue("2. array size", jsonArray.length() == 2); + assertTrue("2. content array entry 0", "if (a < b && a > 0) then return".equals(jsonArray.get(0))); + assertTrue("2. content array entry 1", "here is another cdata".equals(jsonArray.get(1))); + + /* + * text content is accumulated in a "content" inside a local JSONObject. + * If there is only one instance, it is saved in the context (a different JSONObject + * from the calling code. and the content element is discarded. + */ + xmlStr = "value 1"; + jsonObject = XML.toJSONObject(xmlStr, + new XMLParserConfiguration(true, "altContent")); + assertTrue("3. 2 items", 1 == jsonObject.length()); + assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1"))); + + /* + * array-style text content (multiple tags with the same name) is + * accumulated in a local JSONObject with key="content" and value=JSONArray, + * saved in the context, and then the local JSONObject is discarded. + */ + xmlStr = "value 12true"; + jsonObject = XML.toJSONObject(xmlStr, + new XMLParserConfiguration(true, "altContent")); + assertTrue("4. 1 item", 1 == jsonObject.length()); + assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray); + jsonArray = jsonObject.getJSONArray("tag1"); + assertTrue("4. array size", jsonArray.length() == 3); + assertTrue("4. content array entry 0", "value 1".equals(jsonArray.get(0))); + assertTrue("4. content array entry 1", jsonArray.getInt(1) == 2); + assertTrue("4. content array entry 2", jsonArray.getBoolean(2) == true); + + /* + * Complex content is accumulated in a "content" field. For example, an element + * may contain a mix of child elements and text. Each text segment is + * accumulated to content. + */ + xmlStr = "val1val2"; + jsonObject = XML.toJSONObject(xmlStr, + new XMLParserConfiguration(true, "altContent")); + assertTrue("5. 1 item", 1 == jsonObject.length()); + assertTrue("5. jsonObject found", jsonObject.get("tag1") + instanceof JSONObject); + jsonObject = jsonObject.getJSONObject("tag1"); + assertTrue("5. 2 contained items", 2 == jsonObject.length()); + assertTrue("5. contained tag", "".equals(jsonObject.get("tag2"))); + assertTrue("5. contained content jsonArray found", + jsonObject.get("altContent") instanceof JSONArray); + jsonArray = jsonObject.getJSONArray("altContent"); + assertTrue("5. array size", jsonArray.length() == 2); + assertTrue("5. content array entry 0", "val1".equals(jsonArray.get(0))); + assertTrue("5. content array entry 1", "val2".equals(jsonArray.get(1))); + + /* + * If there is only 1 complex text content, then it is accumulated in a + * "content" field as a string. + */ + xmlStr = "val1"; + jsonObject = XML.toJSONObject(xmlStr, + new XMLParserConfiguration(true, "altContent")); + assertTrue("6. 1 item", 1 == jsonObject.length()); + assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); + jsonObject = jsonObject.getJSONObject("tag1"); + assertTrue("6. contained content found", + "val1".equals(jsonObject.get("altContent"))); + assertTrue("6. contained tag2", "".equals(jsonObject.get("tag2"))); + + /* + * In this corner case, the content sibling happens to have key=content + * We end up with an array within an array, and no content element. + * This is probably a bug. + */ + xmlStr = "val1"; + jsonObject = XML.toJSONObject(xmlStr, + new XMLParserConfiguration(true, "altContent")); + assertTrue("7. 1 item", 1 == jsonObject.length()); + assertTrue("7. jsonArray found", + jsonObject.get("tag1") instanceof JSONArray); + jsonArray = jsonObject.getJSONArray("tag1"); + assertTrue("array size 1", jsonArray.length() == 1); + assertTrue("7. contained array found", jsonArray.get(0) + instanceof JSONArray); + jsonArray = jsonArray.getJSONArray(0); + assertTrue("7. inner array size 2", jsonArray.length() == 2); + assertTrue("7. inner array item 0", "val1".equals(jsonArray.get(0))); + assertTrue("7. inner array item 1", "".equals(jsonArray.get(1))); + + /* + * Confirm behavior of original issue + */ + String jsonStr = + "{"+ + "\"Profile\": {"+ + "\"list\": {"+ + "\"history\": {"+ + "\"entries\": ["+ + "{"+ + "\"deviceId\": \"id\","+ + "\"altContent\": {"+ + "\"material\": ["+ + "{"+ + "\"stuff\": false"+ + "}"+ + "]"+ + "}"+ + "}"+ + "]"+ + "}"+ + "}"+ + "}"+ + "}"; + jsonObject = new JSONObject(jsonStr); + xmlStr = XML.toString(jsonObject, null, + new XMLParserConfiguration(true, "altContent")); + /* + * This is the created XML. Looks like content was mistaken for + * complex (child node + text) XML. + * + * + * + * + * id + * {"material":[{"stuff":false}]} + * + * + * + * + */ + assertTrue("nothing to test here, see comment on created XML, above", true); + } + + /** + * JSON string lost leading zero and converted "True" to true. + */ + @Test + public void testToJSONArray_jsonOutput() { + final String originalXml = "011000True"; + final String expectedJsonString = "{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}"; + final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, + new XMLParserConfiguration(false)); + assertEquals(expectedJsonString, actualJsonOutput.toString()); + } + + /** + * JSON string cannot be reverted to original xml. + */ + @Test + public void testToJSONArray_reversibility() { + final String originalXml = "011000True"; + XMLParserConfiguration config = new XMLParserConfiguration(false); + final String revertedXml = + XML.toString(XML.toJSONObject(originalXml, config), + null, config); + assertNotEquals(revertedXml, originalXml); + } + + /** + * test passes when using the new method toJsonArray. + */ + @Test + public void testToJsonXML() { + final String originalXml = "011000True"; + final String expectedJsonString = "{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"; + + final JSONObject json = XML.toJSONObject(originalXml, + new XMLParserConfiguration(true)); + assertEquals(expectedJsonString, json.toString()); + + final String reverseXml = XML.toString(json); + // this reversal isn't exactly the same. use JSONML for an exact reversal + final String expectedReverseXml = "01011000True"; + + assertEquals(expectedReverseXml, reverseXml); + } + + /** + * test to validate certain conditions of XML unescaping. + */ + @Test + public void testUnescape() { + assertEquals("{\"xml\":\"Can cope <;\"}", + XML.toJSONObject("Can cope <; ", + XMLParserConfiguration.KEEP_STRINGS).toString()); + assertEquals("Can cope <; ", XML.unescape("Can cope <; ")); + + assertEquals("{\"xml\":\"Can cope & ;\"}", + XML.toJSONObject("Can cope & ; ", + XMLParserConfiguration.KEEP_STRINGS).toString()); + assertEquals("Can cope & ; ", XML.unescape("Can cope & ; ")); + + assertEquals("{\"xml\":\"Can cope &;\"}", + XML.toJSONObject("Can cope &; ", + XMLParserConfiguration.KEEP_STRINGS).toString()); + assertEquals("Can cope &; ", XML.unescape("Can cope &; ")); + + // unicode entity + assertEquals("{\"xml\":\"Can cope 4;\"}", + XML.toJSONObject("Can cope 4; ", + XMLParserConfiguration.KEEP_STRINGS).toString()); + assertEquals("Can cope 4; ", XML.unescape("Can cope 4; ")); + + // double escaped + assertEquals("{\"xml\":\"Can cope <\"}", + XML.toJSONObject("Can cope &lt; ", + XMLParserConfiguration.KEEP_STRINGS).toString()); + assertEquals("Can cope < ", XML.unescape("Can cope &lt; ")); + + assertEquals("{\"xml\":\"Can cope 4\"}", + XML.toJSONObject("Can cope &#x34; ", + XMLParserConfiguration.KEEP_STRINGS).toString()); + assertEquals("Can cope 4 ", XML.unescape("Can cope &#x34; ")); + + } + + /** + * Confirm XMLParserConfiguration functionality + */ + @Test + public void testConfig() { + /** + * 1st param is whether to keep the raw string, or call + * XML.stringToValue(), which may convert the token to + * boolean, null, or number. + * 2nd param is what JSON name to use for strings that are + * evaluated as xml content data in complex objects, e.g. + * + * value + * content data + * + */ + + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " content 1\n"+ + " Sherlock Holmes\n"+ + " content 2\n"+ + " Baker street 5\n"+ + " content 3\n"+ + " 1\n"+ + "
\n"+ + "
"; + + // keep strings, use the altContent tag + XMLParserConfiguration config = + new XMLParserConfiguration(true, "altContent"); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + // num is parsed as a string + assertEquals(jsonObject.getJSONObject("addresses"). + getJSONObject("address").getString("num"), "1"); + // complex content is collected in an 'altContent' array + JSONArray jsonArray = jsonObject.getJSONObject("addresses"). + getJSONObject("address").getJSONArray("altContent"); + String expectedStr = "[\"content 1\", \"content 2\", \"content 3\"]"; + JSONArray expectedJsonArray = new JSONArray(expectedStr); + Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); + + // keepstrings only + jsonObject = XML.toJSONObject(xmlStr, + XMLParserConfiguration.KEEP_STRINGS); + // num is parsed as a string + assertEquals(jsonObject.getJSONObject("addresses"). + getJSONObject("address").getString("num"), "1"); + // complex content is collected in an 'content' array + jsonArray = jsonObject.getJSONObject("addresses"). + getJSONObject("address").getJSONArray("content"); + expectedJsonArray = new JSONArray(expectedStr); + Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); + + // use alternate content name + config = new XMLParserConfiguration("altContent"); + jsonObject = XML.toJSONObject(xmlStr, config); + // num is parsed as a number + assertEquals(jsonObject.getJSONObject("addresses"). + getJSONObject("address").getInt("num"), 1); + // complex content is collected in an 'altContent' array + jsonArray = jsonObject.getJSONObject("addresses"). + getJSONObject("address").getJSONArray("altContent"); + expectedJsonArray = new JSONArray(expectedStr); + Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); + + } + + + /** + * Convenience method, given an input string and expected result, + * convert to JSONObject and compare actual to expected result. + * @param xmlStr the string to parse + * @param expectedStr the expected JSON string + * @param config provides more flexible XML parsing + * flexible XML parsing. + */ + private void compareStringToJSONObject(String xmlStr, String expectedStr, + XMLParserConfiguration config) { + JSONObject expectedJsonObject = new JSONObject(expectedStr); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + } + + /** + * Convenience method, given an input string and expected result, + * convert to JSONObject via reader and compare actual to expected result. + * @param xmlStr the string to parse + * @param expectedStr the expected JSON string + * @param config provides more flexible XML parsing + */ + private void compareReaderToJSONObject(String xmlStr, String expectedStr, + XMLParserConfiguration config) { + /* + * Commenting out this method until the JSON-java code is updated + * to support XML.toJSONObject(reader) + JSONObject expectedJsonObject = new JSONObject(expectedStr); + Reader reader = new StringReader(xmlStr); + JSONObject jsonObject = XML.toJSONObject(reader); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + */ + } + + /** + * Convenience method, given an input string and expected result, convert to + * JSONObject via file and compare actual to expected result. + * + * @param xmlStr + * the string to parse + * @param expectedStr + * the expected JSON string + * @throws IOException + */ + private void compareFileToJSONObject(String xmlStr, String expectedStr) { + /* + * Commenting out this method until the JSON-java code is updated + * to support XML.toJSONObject(reader) + try { + JSONObject expectedJsonObject = new JSONObject(expectedStr); + File tempFile = testFolder.newFile("fileToJSONObject.xml"); + FileWriter fileWriter = new FileWriter(tempFile); + fileWriter.write(xmlStr); + fileWriter.close(); + Reader reader = new FileReader(tempFile); + JSONObject jsonObject = XML.toJSONObject(reader); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + } catch (IOException e) { + assertTrue("file writer error: " +e.getMessage(), false); + } + */ + } +} \ No newline at end of file diff --git a/src/test/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java similarity index 74% rename from src/test/org/json/junit/XMLTest.java rename to src/test/java/org/json/junit/XMLTest.java index d35c8ac..b74daff 100644 --- a/src/test/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1,14 +1,23 @@ package org.json.junit; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.XML; +import org.json.XMLParserConfiguration; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -34,7 +43,7 @@ public class XMLTest { public void shouldHandleNullXML() { String xmlStr = null; JSONObject jsonObject = XML.toJSONObject(xmlStr); - assertTrue("jsonObject should be empty", jsonObject.length() == 0); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); } /** @@ -45,7 +54,7 @@ public void shouldHandleEmptyXML() { String xmlStr = ""; JSONObject jsonObject = XML.toJSONObject(xmlStr); - assertTrue("jsonObject should be empty", jsonObject.length() == 0); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); } /** @@ -55,7 +64,7 @@ public void shouldHandleEmptyXML() { public void shouldHandleNonXML() { String xmlStr = "{ \"this is\": \"not xml\"}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); - assertTrue("xml string should be empty", jsonObject.length() == 0); + assertTrue("xml string should be empty", jsonObject.isEmpty()); } /** @@ -75,11 +84,11 @@ public void shouldHandleInvalidSlashInTag() { ""; try { XML.toJSONObject(xmlStr); - assertTrue("Expecting a JSONException", false); + fail("Expecting a JSONException"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misshaped tag at 176 [character 14 line 5]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misshaped tag at 176 [character 14 line 4]", + e.getMessage()); } } @@ -100,11 +109,11 @@ public void shouldHandleInvalidBangInTag() { ""; try { XML.toJSONObject(xmlStr); - assertTrue("Expecting a JSONException", false); + fail("Expecting a JSONException"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misshaped meta tag at 215 [character 13 line 8]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misshaped meta tag at 214 [character 12 line 7]", + e.getMessage()); } } @@ -125,11 +134,11 @@ public void shouldHandleInvalidBangNoCloseInTag() { ""; try { XML.toJSONObject(xmlStr); - assertTrue("Expecting a JSONException", false); + fail("Expecting a JSONException"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misshaped meta tag at 214 [character 13 line 8]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misshaped meta tag at 213 [character 12 line 7]", + e.getMessage()); } } @@ -150,11 +159,11 @@ public void shouldHandleNoCloseStartTag() { ""; try { XML.toJSONObject(xmlStr); - assertTrue("Expecting a JSONException", false); + fail("Expecting a JSONException"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Misplaced '<' at 193 [character 4 line 7]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Misplaced '<' at 193 [character 4 line 6]", + e.getMessage()); } } @@ -175,11 +184,11 @@ public void shouldHandleInvalidCDATABangInTag() { ""; try { XML.toJSONObject(xmlStr); - assertTrue("Expecting a JSONException", false); + fail("Expecting a JSONException"); } catch (JSONException e) { - assertTrue("Expecting an exception message", - "Expected 'CDATA[' at 204 [character 11 line 6]". - equals(e.getMessage())); + assertEquals("Expecting an exception message", + "Expected 'CDATA[' at 204 [character 11 line 5]", + e.getMessage()); } } @@ -200,7 +209,7 @@ public void shouldHandleNullJSONXML() { public void shouldHandleEmptyJSONXML() { JSONObject jsonObject= new JSONObject(); String xmlStr = XML.toString(jsonObject); - assertTrue("xml string should be empty", xmlStr.length() == 0); + assertTrue("xml string should be empty", xmlStr.isEmpty()); } /** @@ -265,6 +274,63 @@ public void shouldHandleSimpleXML() { compareFileToJSONObject(xmlStr, expectedStr); } + /** + * Tests to verify that supported escapes in XML are converted to actual values. + */ + @Test + public void testXmlEscapeToJson(){ + String xmlStr = + "\n"+ + ""+ + "\""+ + "A €33"+ + "A €22€"+ + "some text ©"+ + "" " & ' < >"+ + "𝄢 𐅥" + + ""; + String expectedStr = + "{\"root\":{" + + "\"rawQuote\":\"\\\"\"," + + "\"euro\":\"A €33\"," + + "\"euroX\":\"A €22€\"," + + "\"unknown\":\"some text ©\"," + + "\"known\":\"\\\" \\\" & ' < >\"," + + "\"high\":\"𝄢 𐅥\""+ + "}}"; + + compareStringToJSONObject(xmlStr, expectedStr); + compareReaderToJSONObject(xmlStr, expectedStr); + compareFileToJSONObject(xmlStr, expectedStr); + } + + /** + * Tests that control characters are escaped. + */ + @Test + public void testJsonToXmlEscape(){ + final String jsonSrc = "{\"amount\":\"10,00 €\"," + + "\"description\":\"Ação Válida\u0085\"," + + "\"xmlEntities\":\"\\\" ' & < >\"" + + "}"; + JSONObject json = new JSONObject(jsonSrc); + String xml = XML.toString(json); + //test control character not existing + assertFalse("Escaping \u0085 failed. Found in XML output.", xml.contains("\u0085")); + assertTrue("Escaping \u0085 failed. Entity not found in XML output.", xml.contains("…")); + // test normal unicode existing + assertTrue("Escaping € failed. Not found in XML output.", xml.contains("€")); + assertTrue("Escaping ç failed. Not found in XML output.", xml.contains("ç")); + assertTrue("Escaping ã failed. Not found in XML output.", xml.contains("ã")); + assertTrue("Escaping á failed. Not found in XML output.", xml.contains("á")); + // test XML Entities converted + assertTrue("Escaping \" failed. Not found in XML output.", xml.contains(""")); + assertTrue("Escaping ' failed. Not found in XML output.", xml.contains("'")); + assertTrue("Escaping & failed. Not found in XML output.", xml.contains("&")); + assertTrue("Escaping < failed. Not found in XML output.", xml.contains("<")); + assertTrue("Escaping > failed. Not found in XML output.", xml.contains(">")); + } + /** * Valid XML with comments to JSONObject */ @@ -398,9 +464,9 @@ public void shouldHandleEmptyArray(){ final String expected = ""; String output1 = XML.toString(jo1,"jo"); - assertTrue("Expected an empty root tag", expected.equals(output1)); + assertEquals("Expected an empty root tag", expected, output1); String output2 = XML.toString(jo2,"jo"); - assertTrue("Expected an empty root tag", expected.equals(output2)); + assertEquals("Expected an empty root tag", expected, output2); } /** @@ -415,9 +481,9 @@ public void shouldHandleEmptyMultiArray(){ final String expected = "OneFour"; String output1 = XML.toString(jo1,"jo"); - assertTrue("Expected a matching array", expected.equals(output1)); + assertEquals("Expected a matching array", expected, output1); String output2 = XML.toString(jo2,"jo"); - assertTrue("Expected a matching array", expected.equals(output2)); + assertEquals("Expected a matching array", expected, output2); } /** @@ -432,9 +498,9 @@ public void shouldHandleNonEmptyArray(){ final String expected = "OneTwoThree"; String output1 = XML.toString(jo1,"jo"); - assertTrue("Expected a non empty root tag", expected.equals(output1)); + assertEquals("Expected a non empty root tag", expected, output1); String output2 = XML.toString(jo2,"jo"); - assertTrue("Expected a non empty root tag", expected.equals(output2)); + assertEquals("Expected a non empty root tag", expected, output2); } /** @@ -449,9 +515,9 @@ public void shouldHandleMultiArray(){ final String expected = "OneTwoThreeFour"; String output1 = XML.toString(jo1,"jo"); - assertTrue("Expected a matching array", expected.equals(output1)); + assertEquals("Expected a matching array", expected, output1); String output2 = XML.toString(jo2,"jo"); - assertTrue("Expected a matching array", expected.equals(output2)); + assertEquals("Expected a matching array", expected, output2); } /** @@ -673,8 +739,8 @@ public void contentOperations() { * @param expectedStr the expected JSON string */ private void compareStringToJSONObject(String xmlStr, String expectedStr) { - JSONObject expectedJsonObject = new JSONObject(expectedStr); JSONObject jsonObject = XML.toJSONObject(xmlStr); + JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } @@ -685,14 +751,10 @@ private void compareStringToJSONObject(String xmlStr, String expectedStr) { * @param expectedStr the expected JSON string */ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { - /* - * Commenting out this method until the JSON-java code is updated - * to support XML.toJSONObject(reader) JSONObject expectedJsonObject = new JSONObject(expectedStr); Reader reader = new StringReader(xmlStr); JSONObject jsonObject = XML.toJSONObject(reader); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); - */ } /** @@ -706,21 +768,116 @@ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { * @throws IOException */ private void compareFileToJSONObject(String xmlStr, String expectedStr) { - /* - * Commenting out this method until the JSON-java code is updated - * to support XML.toJSONObject(reader) try { JSONObject expectedJsonObject = new JSONObject(expectedStr); - File tempFile = testFolder.newFile("fileToJSONObject.xml"); - FileWriter fileWriter = new FileWriter(tempFile); - fileWriter.write(xmlStr); - fileWriter.close(); - Reader reader = new FileReader(tempFile); - JSONObject jsonObject = XML.toJSONObject(reader); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + File tempFile = this.testFolder.newFile("fileToJSONObject.xml"); + try(FileWriter fileWriter = new FileWriter(tempFile);){ + fileWriter.write(xmlStr); + } + try(Reader reader = new FileReader(tempFile);){ + JSONObject jsonObject = XML.toJSONObject(reader); + Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + } } catch (IOException e) { - assertTrue("file writer error: " +e.getMessage(), false); + fail("file writer error: " +e.getMessage()); } - */ + } + + /** + * JSON string lost leading zero and converted "True" to true. + */ + @Test + public void testToJSONArray_jsonOutput() { + final String originalXml = "011000True"; + final String expectedJsonString = "{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}"; + final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false); + + assertEquals(expectedJsonString, actualJsonOutput.toString()); + } + + /** + * JSON string cannot be reverted to original xml. + */ + @Test + public void testToJSONArray_reversibility() { + final String originalXml = "011000True"; + final String revertedXml = XML.toString(XML.toJSONObject(originalXml, false)); + + assertNotEquals(revertedXml, originalXml); + } + + /** + * test passes when using the new method toJsonArray. + */ + @Test + public void testToJsonXML() { + final String originalXml = "011000True"; + final String expectedJsonString = "{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"; + + final JSONObject json = XML.toJSONObject(originalXml,true); + assertEquals(expectedJsonString, json.toString()); + + final String reverseXml = XML.toString(json); + // this reversal isn't exactly the same. use JSONML for an exact reversal + final String expectedReverseXml = "01011000True"; + + assertEquals(expectedReverseXml, reverseXml); + } + + /** + * test to validate certain conditions of XML unescaping. + */ + @Test + public void testUnescape() { + assertEquals("{\"xml\":\"Can cope <;\"}", + XML.toJSONObject("Can cope <; ").toString()); + assertEquals("Can cope <; ", XML.unescape("Can cope <; ")); + + assertEquals("{\"xml\":\"Can cope & ;\"}", + XML.toJSONObject("Can cope & ; ").toString()); + assertEquals("Can cope & ; ", XML.unescape("Can cope & ; ")); + + assertEquals("{\"xml\":\"Can cope &;\"}", + XML.toJSONObject("Can cope &; ").toString()); + assertEquals("Can cope &; ", XML.unescape("Can cope &; ")); + + // unicode entity + assertEquals("{\"xml\":\"Can cope 4;\"}", + XML.toJSONObject("Can cope 4; ").toString()); + assertEquals("Can cope 4; ", XML.unescape("Can cope 4; ")); + + // double escaped + assertEquals("{\"xml\":\"Can cope <\"}", + XML.toJSONObject("Can cope &lt; ").toString()); + assertEquals("Can cope < ", XML.unescape("Can cope &lt; ")); + + assertEquals("{\"xml\":\"Can cope 4\"}", + XML.toJSONObject("Can cope &#x34; ").toString()); + assertEquals("Can cope 4 ", XML.unescape("Can cope &#x34; ")); + + } + + /** + * test passes when xsi:nil="true" converting to null (JSON specification-like nil conversion enabled) + */ + @Test + public void testToJsonWithNullWhenNilConversionEnabled() { + final String originalXml = ""; + final String expectedJsonString = "{\"root\":{\"id\":null}}"; + + final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration(false, "content", true)); + assertEquals(expectedJsonString, json.toString()); + } + + /** + * test passes when xsi:nil="true" not converting to null (JSON specification-like nil conversion disabled) + */ + @Test + public void testToJsonWithNullWhenNilConversionDisabled() { + final String originalXml = ""; + final String expectedJsonString = "{\"root\":{\"id\":{\"xsi:nil\":true}}}"; + + final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration()); + assertEquals(expectedJsonString, json.toString()); } } \ No newline at end of file diff --git a/src/test/java/org/json/junit/data/BrokenToString.java b/src/test/java/org/json/junit/data/BrokenToString.java new file mode 100644 index 0000000..585d751 --- /dev/null +++ b/src/test/java/org/json/junit/data/BrokenToString.java @@ -0,0 +1,13 @@ +package org.json.junit.data; + +/** + * test class for verifying write errors. + * @author John Aylward + * + */ +public class BrokenToString { + @Override + public String toString() { + throw new IllegalStateException("Something went horribly wrong!"); + } +} \ No newline at end of file diff --git a/src/test/java/org/json/junit/data/ExceptionalBean.java b/src/test/java/org/json/junit/data/ExceptionalBean.java new file mode 100644 index 0000000..74d78a7 --- /dev/null +++ b/src/test/java/org/json/junit/data/ExceptionalBean.java @@ -0,0 +1,69 @@ +/** + * + */ +package org.json.junit.data; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import org.json.JSONObject; + +/** + * Object for testing the exception handling in {@link JSONObject#populateMap}. + * + * @author John Aylward + */ +public class ExceptionalBean { + /** + * @return a closeable. + */ + public Closeable getCloseable() { + // anonymous inner class did not work... + return new MyCloseable(); + } + + /** + * @return Nothing really. Just can't be void. + * @throws IllegalAccessException + * always thrown + */ + public int getIllegalAccessException() throws IllegalAccessException { + throw new IllegalAccessException("Yup, it's illegal"); + } + + /** + * @return Nothing really. Just can't be void. + * @throws IllegalArgumentException + * always thrown + */ + public int getIllegalArgumentException() throws IllegalArgumentException { + throw new IllegalArgumentException("Yup, it's illegal"); + } + + /** + * @return Nothing really. Just can't be void. + * @throws InvocationTargetException + * always thrown + */ + public int getInvocationTargetException() throws InvocationTargetException { + throw new InvocationTargetException(new Exception("Yup, it's illegal")); + } + + /** My closeable class. */ + public static final class MyCloseable implements Closeable { + + /** + * @return a string + */ + @SuppressWarnings("unused") + public String getString() { + return "Yup, it's closeable"; + } + + @Override + public void close() throws IOException { + throw new IOException("Closing is too hard!"); + } + } +} diff --git a/src/test/java/org/json/junit/data/Fraction.java b/src/test/java/org/json/junit/data/Fraction.java new file mode 100644 index 0000000..c418179 --- /dev/null +++ b/src/test/java/org/json/junit/data/Fraction.java @@ -0,0 +1,180 @@ +package org.json.junit.data; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * basic fraction class, no frills. + * @author John Aylward + * + */ +public class Fraction extends Number implements Comparable { + /** + * serial id. + */ + private static final long serialVersionUID = 1L; + + /** + * value as a big decimal. + */ + private final BigDecimal bigDecimal; + + /** + * value of the denominator. + */ + private final BigInteger denominator; + /** + * value of the numerator. + */ + private final BigInteger numerator; + + /** + * @param numerator + * numerator + * @param denominator + * denominator + */ + public Fraction(final BigInteger numerator, final BigInteger denominator) { + super(); + if (numerator == null || denominator == null) { + throw new IllegalArgumentException("All values must be non-null"); + } + if (denominator.compareTo(BigInteger.ZERO)==0) { + throw new IllegalArgumentException("Divide by zero"); + } + + final BigInteger n; + final BigInteger d; + // normalize fraction + if (denominator.signum()<0) { + n = numerator.negate(); + d = denominator.negate(); + } else { + n = numerator; + d = denominator; + } + this.numerator = n; + this.denominator = d; + if (n.compareTo(BigInteger.ZERO)==0) { + this.bigDecimal = BigDecimal.ZERO; + } else if (n.compareTo(d)==0) {// i.e. 4/4, 10/10 + this.bigDecimal = BigDecimal.ONE; + } else { + this.bigDecimal = new BigDecimal(this.numerator).divide(new BigDecimal(this.denominator), + RoundingMode.HALF_EVEN); + } + } + + /** + * @param numerator + * numerator + * @param denominator + * denominator + */ + public Fraction(final long numerator, final long denominator) { + this(BigInteger.valueOf(numerator),BigInteger.valueOf(denominator)); + } + + /** + * @return the decimal + */ + public BigDecimal bigDecimalValue() { + return this.bigDecimal; + } + + @Override + public int compareTo(final Fraction o) { + // .equals call this, so no .equals compare allowed + + // if they are the same reference, just return equals + if (this == o) { + return 0; + } + + // if my denominators are already equal, just compare the numerators + if (this.denominator.compareTo(o.denominator)==0) { + return this.numerator.compareTo(o.numerator); + } + + // get numerators of common denominators + // a x ay xb + // --- --- = ---- ---- + // b y by yb + final BigInteger thisN = this.numerator.multiply(o.denominator); + final BigInteger otherN = o.numerator.multiply(this.denominator); + + return thisN.compareTo(otherN); + } + + @Override + public double doubleValue() { + return this.bigDecimal.doubleValue(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + final Fraction other = (Fraction) obj; + return this.compareTo(other) == 0; + } + + @Override + public float floatValue() { + return this.bigDecimal.floatValue(); + } + + /** + * @return the denominator + */ + public BigInteger getDenominator() { + return this.denominator; + } + + /** + * @return the numerator + */ + public BigInteger getNumerator() { + return this.numerator; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (this.bigDecimal == null ? 0 : this.bigDecimal.hashCode()); + return result; + } + + @Override + public int intValue() { + return this.bigDecimal.intValue(); + } + + @Override + public long longValue() { + return this.bigDecimal.longValue(); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.numerator + "/" + this.denominator; + } +} diff --git a/src/test/java/org/json/junit/data/GenericBean.java b/src/test/java/org/json/junit/data/GenericBean.java new file mode 100644 index 0000000..da6370d --- /dev/null +++ b/src/test/java/org/json/junit/data/GenericBean.java @@ -0,0 +1,79 @@ +package org.json.junit.data; + +import java.io.StringReader; + +/** + * + * @author John Aylward + * + * @param + * generic number value + */ +public class GenericBean> implements MyBean { + /** + * @param genericValue + * value to initiate with + */ + public GenericBean(T genericValue) { + super(); + this.genericValue = genericValue; + } + + /** */ + protected T genericValue; + /** to be used by the calling test to see how often the getter is called */ + public int genericGetCounter; + /** to be used by the calling test to see how often the setter is called */ + public int genericSetCounter; + + /** @return the genericValue */ + public T getGenericValue() { + this.genericGetCounter++; + return this.genericValue; + } + + /** + * @param genericValue + * generic value to set + */ + public void setGenericValue(T genericValue) { + this.genericSetCounter++; + this.genericValue = genericValue; + } + + @Override + public Integer getIntKey() { + return Integer.valueOf(42); + } + + @Override + public Double getDoubleKey() { + return Double.valueOf(4.2); + } + + @Override + public String getStringKey() { + return "MyString Key"; + } + + @Override + public String getEscapeStringKey() { + return "\"My String with \"s"; + } + + @Override + public Boolean isTrueKey() { + return Boolean.TRUE; + } + + @Override + public Boolean isFalseKey() { + return Boolean.FALSE; + } + + @Override + public StringReader getStringReaderKey() { + return new StringReader("Some String Value in a reader"); + } + +} diff --git a/src/test/java/org/json/junit/data/GenericBeanInt.java b/src/test/java/org/json/junit/data/GenericBeanInt.java new file mode 100644 index 0000000..5056611 --- /dev/null +++ b/src/test/java/org/json/junit/data/GenericBeanInt.java @@ -0,0 +1,69 @@ +/** + * + */ +package org.json.junit.data; + +/** + * @author john + * + */ +public class GenericBeanInt extends GenericBean { + /** */ + final char a = 'A'; + + /** @return the a */ + public char getA() { + return this.a; + } + + /** + * Should not be beanable + * + * @return false + */ + public boolean getable() { + return false; + } + + /** + * Should not be beanable + * + * @return false + */ + public boolean get() { + return false; + } + + /** + * Should not be beanable + * + * @return false + */ + public boolean is() { + return false; + } + + /** + * Should be beanable + * + * @return false + */ + public boolean isB() { + return this.genericValue.equals((Integer.valueOf(this.a+1))); + } + + /** + * @param genericValue + * the value to initiate with. + */ + public GenericBeanInt(Integer genericValue) { + super(genericValue); + } + + /** override to generate a bridge method */ + @Override + public Integer getGenericValue() { + return super.getGenericValue(); + } + +} diff --git a/src/test/org/json/junit/MyBean.java b/src/test/java/org/json/junit/data/MyBean.java similarity index 85% rename from src/test/org/json/junit/MyBean.java rename to src/test/java/org/json/junit/data/MyBean.java index 53d150a..3190981 100644 --- a/src/test/org/json/junit/MyBean.java +++ b/src/test/java/org/json/junit/data/MyBean.java @@ -1,11 +1,11 @@ -package org.json.junit; +package org.json.junit.data; import java.io.*; /** * Used in testing when Bean behavior is needed */ -interface MyBean { +public interface MyBean { public Integer getIntKey(); public Double getDoubleKey(); public String getStringKey(); diff --git a/src/test/java/org/json/junit/data/MyBeanCustomName.java b/src/test/java/org/json/junit/data/MyBeanCustomName.java new file mode 100644 index 0000000..56756c2 --- /dev/null +++ b/src/test/java/org/json/junit/data/MyBeanCustomName.java @@ -0,0 +1,20 @@ +package org.json.junit.data; + +import org.json.JSONPropertyName; + +/** + * Test bean for the {@link JSONPropertyName} annotation. + */ +public class MyBeanCustomName implements MyBeanCustomNameInterface { + public int getSomeInt() { return 42; } + @JSONPropertyName("") + public long getSomeLong() { return 42L; } + @JSONPropertyName("myStringField") + public String getSomeString() { return "someStringValue"; } + @JSONPropertyName("Some Weird NAme that Normally Wouldn't be possible!") + public double getMyDouble() { return 0.0d; } + @Override + public float getSomeFloat() { return 2.0f; } + @Override + public int getIgnoredInt() { return 40; } +} diff --git a/src/test/java/org/json/junit/data/MyBeanCustomNameInterface.java b/src/test/java/org/json/junit/data/MyBeanCustomNameInterface.java new file mode 100644 index 0000000..b25b578 --- /dev/null +++ b/src/test/java/org/json/junit/data/MyBeanCustomNameInterface.java @@ -0,0 +1,11 @@ +package org.json.junit.data; + +import org.json.JSONPropertyIgnore; +import org.json.JSONPropertyName; + +public interface MyBeanCustomNameInterface { + @JSONPropertyName("InterfaceField") + float getSomeFloat(); + @JSONPropertyIgnore + int getIgnoredInt(); +} \ No newline at end of file diff --git a/src/test/java/org/json/junit/data/MyBeanCustomNameSubClass.java b/src/test/java/org/json/junit/data/MyBeanCustomNameSubClass.java new file mode 100644 index 0000000..8f0500c --- /dev/null +++ b/src/test/java/org/json/junit/data/MyBeanCustomNameSubClass.java @@ -0,0 +1,32 @@ +/** + * + */ +package org.json.junit.data; + +import org.json.JSONPropertyIgnore; +import org.json.JSONPropertyName; + +/** + * Test bean to verify that the {@link org.json.JSONPropertyName} annotation + * is inherited. + */ +public class MyBeanCustomNameSubClass extends MyBeanCustomName { + @Override + @JSONPropertyName("forcedInt") + public int getIgnoredInt() { return 42*42; } + @Override + @JSONPropertyName("newIntFieldName") + public int getSomeInt() { return 43; } + @Override + public String getSomeString() { return "subClassString"; } + @Override + @JSONPropertyName("AMoreNormalName") + public double getMyDouble() { return 1.0d; } + @Override + public float getSomeFloat() { return 3.0f; } + @JSONPropertyIgnore + @JSONPropertyName("ShouldBeIgnored") + public boolean getShouldNotBeJSON() { return true; } + @JSONPropertyName("Getable") + public boolean getable() { return true; } +} diff --git a/src/test/org/json/junit/MyBigNumberBean.java b/src/test/java/org/json/junit/data/MyBigNumberBean.java similarity index 72% rename from src/test/org/json/junit/MyBigNumberBean.java rename to src/test/java/org/json/junit/data/MyBigNumberBean.java index 0ca1870..934dfee 100644 --- a/src/test/org/json/junit/MyBigNumberBean.java +++ b/src/test/java/org/json/junit/data/MyBigNumberBean.java @@ -1,11 +1,11 @@ -package org.json.junit; +package org.json.junit.data; import java.math.*; /** * Used in testing when a Bean containing big numbers is needed */ -interface MyBigNumberBean { +public interface MyBigNumberBean { public BigInteger getBigInteger(); public BigDecimal getBigDecimal(); } \ No newline at end of file diff --git a/src/test/org/json/junit/MyEnum.java b/src/test/java/org/json/junit/data/MyEnum.java similarity index 76% rename from src/test/org/json/junit/MyEnum.java rename to src/test/java/org/json/junit/data/MyEnum.java index 0952bc2..50d9a4f 100644 --- a/src/test/org/json/junit/MyEnum.java +++ b/src/test/java/org/json/junit/data/MyEnum.java @@ -1,4 +1,4 @@ -package org.json.junit; +package org.json.junit.data; /** * An enum with no methods or data diff --git a/src/test/org/json/junit/MyEnumClass.java b/src/test/java/org/json/junit/data/MyEnumClass.java similarity index 94% rename from src/test/org/json/junit/MyEnumClass.java rename to src/test/java/org/json/junit/data/MyEnumClass.java index 8e71663..4d403c8 100644 --- a/src/test/org/json/junit/MyEnumClass.java +++ b/src/test/java/org/json/junit/data/MyEnumClass.java @@ -1,4 +1,4 @@ -package org.json.junit; +package org.json.junit.data; /** * this is simply a class that contains some enum instances diff --git a/src/test/org/json/junit/MyEnumField.java b/src/test/java/org/json/junit/data/MyEnumField.java similarity index 68% rename from src/test/org/json/junit/MyEnumField.java rename to src/test/java/org/json/junit/data/MyEnumField.java index cff565a..60e89de 100644 --- a/src/test/org/json/junit/MyEnumField.java +++ b/src/test/java/org/json/junit/data/MyEnumField.java @@ -1,8 +1,9 @@ -package org.json.junit; +package org.json.junit.data; /** * An enum that contains getters and some internal fields */ +@SuppressWarnings("boxing") public enum MyEnumField { VAL1(1, "val 1"), VAL2(2, "val 2"), @@ -15,9 +16,13 @@ private MyEnumField(Integer intVal, String value) { this.intVal = intVal; } public String getValue() { - return value; + return this.value; } public Integer getIntVal() { - return intVal; + return this.intVal; + } + @Override + public String toString(){ + return this.value; } } diff --git a/src/test/org/json/junit/MyJsonString.java b/src/test/java/org/json/junit/data/MyJsonString.java similarity index 67% rename from src/test/org/json/junit/MyJsonString.java rename to src/test/java/org/json/junit/data/MyJsonString.java index 4e63693..4ddde53 100644 --- a/src/test/org/json/junit/MyJsonString.java +++ b/src/test/java/org/json/junit/data/MyJsonString.java @@ -1,11 +1,11 @@ -package org.json.junit; +package org.json.junit.data; import org.json.*; /** * Used in testing when a JSONString is needed */ -class MyJsonString implements JSONString { +public class MyJsonString implements JSONString { @Override public String toJSONString() { diff --git a/src/test/java/org/json/junit/data/MyLocaleBean.java b/src/test/java/org/json/junit/data/MyLocaleBean.java new file mode 100755 index 0000000..846e1c5 --- /dev/null +++ b/src/test/java/org/json/junit/data/MyLocaleBean.java @@ -0,0 +1,12 @@ +package org.json.junit.data; + +public class MyLocaleBean { + private final String id = "beanId"; + private final String i = "beanI"; + public String getId() { + return id; + } + public String getI() { + return i; + } +} diff --git a/src/test/java/org/json/junit/data/MyNumber.java b/src/test/java/org/json/junit/data/MyNumber.java new file mode 100644 index 0000000..4b625af --- /dev/null +++ b/src/test/java/org/json/junit/data/MyNumber.java @@ -0,0 +1,97 @@ +package org.json.junit.data; + +import java.math.BigDecimal; + +/** + * Number override for testing. Number overrides should always override + * toString, hashCode, and Equals. + * + * @see The + * Numbers Classes + * @see Formatting + * Numeric Print Output + * + * @author John Aylward + */ +public class MyNumber extends Number { + private Number number = BigDecimal.valueOf(42); + /** + */ + private static final long serialVersionUID = 1L; + + /** + * @return number! + */ + public Number getNumber() { + return this.number; + } + + @Override + public int intValue() { + return getNumber().intValue(); + } + + @Override + public long longValue() { + return getNumber().longValue(); + } + + @Override + public float floatValue() { + return getNumber().floatValue(); + } + + @Override + public double doubleValue() { + return getNumber().doubleValue(); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + * + * Number overrides should in general always override the toString method. + */ + @Override + public String toString() { + return getNumber().toString(); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.number == null) ? 0 : this.number.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof MyNumber)) { + return false; + } + MyNumber other = (MyNumber) obj; + if (this.number == null) { + if (other.number != null) { + return false; + } + } else if (!this.number.equals(other.number)) { + return false; + } + return true; + } + +} diff --git a/src/test/java/org/json/junit/data/MyNumberContainer.java b/src/test/java/org/json/junit/data/MyNumberContainer.java new file mode 100644 index 0000000..6527652 --- /dev/null +++ b/src/test/java/org/json/junit/data/MyNumberContainer.java @@ -0,0 +1,13 @@ +package org.json.junit.data; + +/** + * Class that holds our MyNumber override as a property. + * @author John Aylward + */ +public class MyNumberContainer { + private MyNumber myNumber = new MyNumber(); + /** + * @return a MyNumber. + */ + public Number getMyNumber() {return this.myNumber;} +} diff --git a/src/test/org/json/junit/MyPublicClass.java b/src/test/java/org/json/junit/data/MyPublicClass.java similarity index 75% rename from src/test/org/json/junit/MyPublicClass.java rename to src/test/java/org/json/junit/data/MyPublicClass.java index 1f55e3e..1f30386 100644 --- a/src/test/org/json/junit/MyPublicClass.java +++ b/src/test/java/org/json/junit/data/MyPublicClass.java @@ -1,8 +1,9 @@ -package org.json.junit; +package org.json.junit.data; /** * Need a class with some public data members for testing */ +@SuppressWarnings("boxing") public class MyPublicClass { public Integer publicInt = 42; public String publicString = "abc"; diff --git a/src/test/java/org/json/junit/data/Singleton.java b/src/test/java/org/json/junit/data/Singleton.java new file mode 100644 index 0000000..36a9824 --- /dev/null +++ b/src/test/java/org/json/junit/data/Singleton.java @@ -0,0 +1,91 @@ +package org.json.junit.data; + +/** + * Sample singleton for use with bean testing. + * + * @author John Aylward + * + */ +public final class Singleton { + /** */ + private int someInt; + /** */ + private String someString; + /** single instance. */ + private static final Singleton INSTANCE = new Singleton(); + + /** @return the singleton instance. */ + public static final Singleton getInstance() { + return INSTANCE; + } + + /** */ + private Singleton() { + if (INSTANCE != null) { + throw new IllegalStateException("Already instantiated"); + } + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return INSTANCE; + } + + /** @return someInt */ + public int getSomeInt() { + return someInt; + } + + /** + * sets someInt. + * + * @param someInt + * the someInt to set + */ + public void setSomeInt(int someInt) { + this.someInt = someInt; + } + + /** @return someString */ + public String getSomeString() { + return someString; + } + + /** + * sets someString. + * + * @param someString + * the someString to set + */ + public void setSomeString(String someString) { + this.someString = someString; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + someInt; + result = prime * result + ((someString == null) ? 0 : someString.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Singleton other = (Singleton) obj; + if (someInt != other.someInt) + return false; + if (someString == null) { + if (other.someString != null) + return false; + } else if (!someString.equals(other.someString)) + return false; + return true; + } +} diff --git a/src/test/java/org/json/junit/data/SingletonEnum.java b/src/test/java/org/json/junit/data/SingletonEnum.java new file mode 100644 index 0000000..8147cc6 --- /dev/null +++ b/src/test/java/org/json/junit/data/SingletonEnum.java @@ -0,0 +1,62 @@ +package org.json.junit.data; + +/** + * Sample singleton done as an Enum for use with bean testing. + * + * @author John Aylward + * + */ +public enum SingletonEnum { + /** + * the singleton instance. + */ + INSTANCE; + /** */ + private int someInt; + /** */ + private String someString; + + /** single instance. */ + + /** + * @return the singleton instance. I a real application, I'd hope no one did + * this to an enum singleton. + */ + public static final SingletonEnum getInstance() { + return INSTANCE; + } + + /** */ + private SingletonEnum() { + } + + /** @return someInt */ + public int getSomeInt() { + return someInt; + } + + /** + * sets someInt. + * + * @param someInt + * the someInt to set + */ + public void setSomeInt(int someInt) { + this.someInt = someInt; + } + + /** @return someString */ + public String getSomeString() { + return someString; + } + + /** + * sets someString. + * + * @param someString + * the someString to set + */ + public void setSomeString(String someString) { + this.someString = someString; + } +} diff --git a/src/test/org/json/junit/StringsResourceBundle.java b/src/test/java/org/json/junit/data/StringsResourceBundle.java similarity index 90% rename from src/test/org/json/junit/StringsResourceBundle.java rename to src/test/java/org/json/junit/data/StringsResourceBundle.java index 83d9322..4479350 100644 --- a/src/test/org/json/junit/StringsResourceBundle.java +++ b/src/test/java/org/json/junit/data/StringsResourceBundle.java @@ -1,4 +1,4 @@ -package org.json.junit; +package org.json.junit.data; import java.util.*; @@ -6,6 +6,7 @@ * A resource bundle class */ public class StringsResourceBundle extends ListResourceBundle { + @Override public Object[][] getContents() { return contents; } diff --git a/src/test/java/org/json/junit/data/WeirdList.java b/src/test/java/org/json/junit/data/WeirdList.java new file mode 100644 index 0000000..77cd17f --- /dev/null +++ b/src/test/java/org/json/junit/data/WeirdList.java @@ -0,0 +1,67 @@ +/** + * + */ +package org.json.junit.data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author John Aylward + */ +public class WeirdList { + /** */ + private final List list = new ArrayList<>(); + + /** + * @param vals + */ + public WeirdList(Integer... vals) { + this.list.addAll(Arrays.asList(vals)); + } + + /** + * @return a copy of the list + */ + public List get() { + return new ArrayList<>(this.list); + } + + /** + * @return a copy of the list + */ + public List getALL() { + return new ArrayList<>(this.list); + } + + /** + * get a value at an index. + * + * @param i + * index to get + * @return the value at the index + */ + public Integer get(int i) { + return this.list.get(i); + } + + /** + * get a value at an index. + * + * @param i + * index to get + * @return the value at the index + */ + public int getInt(int i) { + return this.list.get(i); + } + + /** + * @param value + * new value to add to the end of the list + */ + public void add(Integer value) { + this.list.add(value); + } +} \ No newline at end of file diff --git a/src/test/resources/jsonpointer-testdoc.json b/src/test/resources/jsonpointer-testdoc.json index d58fe82..657ccdd 100644 --- a/src/test/resources/jsonpointer-testdoc.json +++ b/src/test/resources/jsonpointer-testdoc.json @@ -19,6 +19,10 @@ "another/key" : [ "val" ] + }, + "" : { + "" : "empty key of an object with an empty key", + "subKey" : "Some other value" } } } \ No newline at end of file