53 Commits

Author SHA1 Message Date
Sean Leary
24fafcffeb Merge pull request #981 from stleary/pre-release-20250517
pre-release-20250517 prep for next release
2025-05-17 07:44:38 -05:00
Sean Leary
418d5e9973 pre-release-20250517 prep for next release 2025-05-17 07:41:21 -05:00
Sean Leary
82a02d879e Merge pull request #969 from marilynel/master
refactored large test for strict mode
2025-04-18 11:48:25 -05:00
marilynel
2184ef34d1 refactored large test for strict mode 2025-04-13 11:35:45 -07:00
Sean Leary
8e65eaa992 Merge pull request #968 from marilynel/master
Update keepStrings behavior to reflect changes in keepBooleanAsString, keepNumberAsString
2025-04-10 11:56:36 -05:00
marilynel
74439cf696 Merge branch 'master' of https://github.com/marilynel/JSON-java 2025-04-06 11:05:02 -07:00
marilynel
53da5ce2a9 adjusted keepstrings behavior to reflect changes in keepBooleanAsString & keepNumberAsString 2025-04-06 11:04:33 -07:00
Sean Leary
2e9ad6ff5a Merge pull request #966 from marilynel/master
granular flags to control for keeping boolean or number values as strings
2025-04-04 07:40:55 -05:00
marilynel
8dbf03e76b work on issue 841 2025-03-30 12:21:44 -07:00
Sean Leary
4917e3579d Merge pull request #962 from marilynel/master
Fix: handles edge case 'no \n at end of csv dataset + empty last column'
2025-03-26 20:24:01 -05:00
marilynel
45ec164faa Merge branch 'master' of https://github.com/marilynel/JSON-java 2025-03-23 10:27:57 -07:00
Sean Leary
d4c5136c21 Merge pull request #961 from effad/master
Option to store null value in JSONObject when parsing a map
2025-03-23 10:21:53 -05:00
Robert Lichtenberger
fd0cca3586 Fix cloning of parser configuration. 2025-03-21 10:12:20 +01:00
Robert Lichtenberger
50a5ce256b Merge branch 'stleary:master' into master 2025-03-21 07:27:12 +01:00
Robert Lichtenberger
1afd7cd6bc Use better name for parser configuration option, fix API comment. 2025-03-21 07:25:37 +01:00
Sean Leary
7751b397bf Merge pull request #960 from marilynel/master
Updated configuration to enable strictMode unit testing with Maven
2025-03-19 11:39:35 -05:00
Robert Lichtenberger
5d1c789490 Add test for JSONArray from Java collection. 2025-03-19 08:10:33 +01:00
Robert Lichtenberger
d1327c2da3 Allow to configure Java null handling. 2025-03-19 07:59:57 +01:00
marilynel
b2943b8fd0 fixed issue #943 Csv parsing skip last row if last line is missing newline 2025-03-16 12:50:58 -07:00
Marilyn Leary
628d8c42d9 Merge branch 'stleary:master' into master 2025-03-16 10:38:04 -07:00
marilynel
76ee4312b3 readme edit 2025-03-16 10:36:24 -07:00
marilynel
4a662316f7 edited pom.xml for mvn testing with strict mode 2025-03-16 10:33:14 -07:00
Sean Leary
6452a6f38d Merge pull request #955 from marilynel/master
Add testWithStrictMode option to build.gradle
2025-03-05 20:30:24 -06:00
marilynel
ae4f4afcc7 dont mess with my line 2025-03-02 11:08:00 -08:00
marilynel
8a86894c63 test with strict mode enabled and fixed 2025-03-02 11:02:27 -08:00
marilynel
f30167e7c0 tests seem to be working, run with strictMode = fale then true 2025-02-23 22:00:22 -08:00
Sean Leary
75e5a3d646 Merge pull request #951 from marilynel/master
Fixing and updating unit tests for default strictMode
2025-02-21 07:37:47 -06:00
marilynel
3919abd69a optimized unit tests to respond accurately to default strictMode 2025-02-15 12:30:12 -08:00
marilynel
f112a091aa fixed failing unit tests in strict mode, issue 940 2025-02-15 12:03:03 -08:00
Sean Leary
42afb34045 Merge pull request #949 from marilynel/master
deprecated unnecessary setter method
2025-02-15 09:59:56 -06:00
Marilyn Leary
a746322e57 Merge branch 'stleary:master' into master 2025-02-09 19:36:46 -08:00
Sean Leary
c524cd17a0 Merge pull request #950 from stleary/upgrade-upload-artifact-in-pipeline
upgrade-upload-artifact-in-pipeline update from v3 to v4
2025-02-09 15:01:26 -06:00
Sean Leary
52f249c71e upgrade-upload-artifact-in-pipeline update from v3 to v4 2025-02-09 14:47:18 -06:00
marilynel
1689fc28cf deprecated unnecessary setter method 2025-02-09 11:13:22 -08:00
Sean Leary
22f8290840 Merge pull request #948 from Simulant87/947-JSONTokener-configuration-ignored
use JSONParserConfiguration of JSONTokener in JSONObject and JSONArray constructor instead of creating a new one
2025-01-19 09:09:42 -06:00
Sean Leary
8b857da467 Merge pull request #946 from Simulant87/928-javadoc-warning-JSONParserConfiguration
#928 add missing javaDoc for JSONParserConfiguration
2025-01-19 09:07:53 -06:00
Sean Leary
07b1291448 Merge pull request #942 from michael-ameri/fix-clone
add missing fields when cloning JSONParserConfiguration
2025-01-19 09:06:21 -06:00
Sean Leary
1d81e8879a Merge pull request #938 from Simulant87/remove-references
Strict mode unit tests
2025-01-19 09:03:47 -06:00
Simulant
4c873a1db4 #947 use JSONParserConfiguration of JSONTokener in JSONObject and JSONArray constructor 2025-01-15 21:41:01 +01:00
Simulant
6631b80e8f #947 add new failing tests with JSONTokener having strict mode configuration 2025-01-15 21:38:46 +01:00
Simulant
9218f28db8 #928 add missing java dock for JSONParserConfiguration
(cherry picked from commit afd9a6fbb7)
2025-01-15 21:02:25 +01:00
Simulant
94341cd663 Revert "#928 add missing java dock for JSONParserConfiguration"
This reverts commit afd9a6fbb7.
2025-01-15 20:58:45 +01:00
Simulant
afd9a6fbb7 #928 add missing java dock for JSONParserConfiguration 2025-01-15 20:55:13 +01:00
Simulant87
54470e6b56 Merge branch 'stleary:master' into remove-references 2025-01-15 20:48:24 +01:00
Sean Leary
dde9d7eceb Merge pull request #931 from stleary/remove-duplicate-moditect
remove-duplicate-moditect: from pom.xml
2025-01-15 07:46:37 -06:00
Sean Leary
8c427d9101 Merge pull request #937 from michael-ameri/remove-references
Update JSONParserConfiguration usage in JSONTokener, JSONArray, and JSONObject
2025-01-15 07:45:50 -06:00
Michael Ameri
4bbbe77446 add missing fields when cloning 2025-01-12 23:03:31 +01:00
Simulant
ad44a9274c add new test cases for JSONObject and JSONArray Constructors with JSONTokener and strict mode 2025-01-11 21:43:04 +01:00
Simulant
3b7ba07531 add test for invalid input on JSONTokener 2025-01-11 21:40:41 +01:00
Simulant
215f4268bf add Javadoc and rename parameters to speaking variable names 2025-01-11 21:35:36 +01:00
Michael Ameri
ca1c6830c9 remove field references to JSONTokener and JSONParserConfiguration in JSONArray
and JSONObject
2025-01-10 18:05:27 +01:00
Sean Leary
2e153737b1 remove-duplicate-moditect: from pom.xml 2025-01-08 07:33:37 -06:00
Sean Leary
391c86931b Merge pull request #930 from stleary/pre-release-20250107
pre-release-20250107
2025-01-07 15:40:58 -06:00
18 changed files with 1164 additions and 473 deletions

View File

@@ -30,7 +30,7 @@ jobs:
jar cvf target/org.json.jar -C target/classes .
- name: Upload JAR 1.6
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Create java 1.6 JAR
path: target/*.jar
@@ -64,13 +64,13 @@ jobs:
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Results ${{ matrix.java }}
path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Report ${{ matrix.java }}
path: target/site/
@@ -78,7 +78,7 @@ jobs:
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
- name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Package Jar ${{ matrix.java }}
path: target/*.jar
@@ -112,13 +112,13 @@ jobs:
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Results ${{ matrix.java }}
path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Report ${{ matrix.java }}
path: target/site/
@@ -126,7 +126,7 @@ jobs:
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
- name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Package Jar ${{ matrix.java }}
path: target/*.jar
@@ -160,13 +160,13 @@ jobs:
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Results ${{ matrix.java }}
path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Report ${{ matrix.java }}
path: target/site/
@@ -174,7 +174,7 @@ jobs:
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
- name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Package Jar ${{ matrix.java }}
path: target/*.jar
@@ -208,13 +208,13 @@ jobs:
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Results ${{ matrix.java }}
path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Test Report ${{ matrix.java }}
path: target/site/
@@ -222,7 +222,7 @@ jobs:
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
- name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Package Jar ${{ matrix.java }}
path: target/*.jar

View File

@@ -10,7 +10,7 @@ JSON in Java [package org.json]
[![Java CI with Maven](https://github.com/stleary/JSON-java/actions/workflows/pipeline.yml/badge.svg)](https://github.com/stleary/JSON-java/actions/workflows/pipeline.yml)
[![CodeQL](https://github.com/stleary/JSON-java/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/stleary/JSON-java/actions/workflows/codeql-analysis.yml)
**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20250107/json-20250107.jar)**
**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20250517/json-20250517.jar)**
# Overview
@@ -97,6 +97,18 @@ Execute the test suite with Gradlew:
gradlew clean build test
```
*Optional* Execute the test suite in strict mode with Gradlew:
```shell
gradlew testWithStrictMode
```
*Optional* Execute the test suite in strict mode with Maven:
```shell
mvn test -P test-strict-mode
```
# Notes
For more information, please see [NOTES.md](https://github.com/stleary/JSON-java/blob/master/docs/NOTES.md)

View File

@@ -30,7 +30,7 @@ subprojects {
}
group = 'org.json'
version = 'v20250107-SNAPSHOT'
version = 'v20250517-SNAPSHOT'
description = 'JSON in Java'
sourceCompatibility = '1.8'
@@ -53,3 +53,75 @@ publishing {
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
// Add these imports at the top of your build.gradle file
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
// Your existing build configurations...
// Add a new task to modify the file
task modifyStrictMode {
doLast {
println "Modifying JSONParserConfiguration.java to enable strictMode..."
def filePath = project.file('src/main/java/org/json/JSONParserConfiguration.java')
if (!filePath.exists()) {
throw new GradleException("Could not find file: ${filePath.absolutePath}")
}
// Create a backup of the original file
def backupFile = new File(filePath.absolutePath + '.bak')
Files.copy(filePath.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
// Read and modify the file content
def content = filePath.text
def modifiedContent = content.replace('// this.strictMode = true;', 'this.strictMode = true;')
// Write the modified content back to the file
filePath.text = modifiedContent
println "File modified successfully at: ${filePath.absolutePath}"
}
}
// Add a task to restore the original file
task restoreStrictMode {
doLast {
println "Restoring original JSONParserConfiguration.java..."
def filePath = project.file('src/main/java/org/json/JSONParserConfiguration.java')
def backupFile = new File(filePath.absolutePath + '.bak')
if (backupFile.exists()) {
Files.copy(backupFile.toPath(), filePath.toPath(), StandardCopyOption.REPLACE_EXISTING)
backupFile.delete()
println "Original file restored successfully at: ${filePath.absolutePath}"
} else {
println "Backup file not found at: ${backupFile.absolutePath}. No restoration performed."
}
}
}
// Create a task to run the workflow
task testWithStrictMode {
dependsOn modifyStrictMode
finalizedBy restoreStrictMode
doLast {
// This will trigger a clean build and run tests with strictMode enabled
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
exec {
executable 'cmd'
args '/c', 'gradlew.bat', 'clean', 'build'
}
} else {
exec {
executable './gradlew'
args 'clean', 'build'
}
}
}
}

View File

@@ -5,9 +5,11 @@ and artifactId "json". For example:
[https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav)
~~~
20250107....Restore moditect in pom.xml
20250517 Strict mode hardening and recent commits
20241224....Strict mode opt-in feature, and recent commits. This release does not contain module-info.class.
20250107 Restore moditect in pom.xml
20241224 Strict mode opt-in feature, and recent commits. This release does not contain module-info.class.
It is not recommended if you need this feature.
20240303 Revert optLong/getLong changes, and recent commits.

77
pom.xml
View File

@@ -3,7 +3,7 @@
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20250107</version>
<version>20250517</version>
<packaging>bundle</packaging>
<name>JSON in Java</name>
@@ -193,30 +193,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<version>1.0.0.Final</version>
<executions>
<execution>
<id>add-module-infos</id>
<phase>package</phase>
<goals>
<goal>add-module-info</goal>
</goals>
<configuration>
<jvmVersion>9</jvmVersion>
<module>
<moduleInfoSource>
module org.json {
exports org.json;
}
</moduleInfoSource>
</module>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -224,4 +200,55 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>test-strict-mode</id>
<build>
<plugins>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>1.5.3</version>
<executions>
<!-- Enable strict mode -->
<execution>
<id>enable-strict-mode</id>
<phase>process-sources</phase>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<file>src/main/java/org/json/JSONParserConfiguration.java</file>
<replacements>
<replacement>
<token>// this.strictMode = true;</token>
<value>this.strictMode = true;</value>
</replacement>
</replacements>
</configuration>
</execution>
<!-- Restore original code after tests -->
<execution>
<id>restore-original</id>
<phase>test</phase>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<file>src/main/java/org/json/JSONParserConfiguration.java</file>
<replacements>
<replacement>
<token>this.strictMode = true;</token>
<value>// this.strictMode = true;</value>
</replacement>
</replacements>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -100,11 +100,15 @@ public class CDL {
for (;;) {
String value = getValue(x,delimiter);
char c = x.next();
if (value == null ||
(ja.length() == 0 && value.length() == 0 && c != delimiter)) {
if (value != null) {
ja.put(value);
} else if (ja.length() == 0 && c != delimiter) {
return null;
} else {
// This line accounts for CSV ending with no newline
ja.put("");
}
ja.put(value);
for (;;) {
if (c == delimiter) {
break;
@@ -307,6 +311,17 @@ public class CDL {
if (ja.length() == 0) {
return null;
}
// The following block accounts for empty datasets (no keys or vals)
if (ja.length() == 1) {
JSONObject j = ja.getJSONObject(0);
if (j.length() == 1) {
String key = j.keys().next();
if ("".equals(key) && "".equals(j.get(key))) {
return null;
}
}
}
return ja;
}

View File

@@ -67,12 +67,6 @@ public class JSONArray implements Iterable<Object> {
*/
private final ArrayList<Object> myArrayList;
// strict mode checks after constructor require access to this object
private JSONTokener jsonTokener;
// strict mode checks after constructor require access to this object
private JSONParserConfiguration jsonParserConfiguration;
/**
* Construct an empty JSONArray.
*/
@@ -89,7 +83,7 @@ public class JSONArray implements Iterable<Object> {
* If there is a syntax error.
*/
public JSONArray(JSONTokener x) throws JSONException {
this(x, new JSONParserConfiguration());
this(x, x.getJsonParserConfiguration());
}
/**
@@ -102,14 +96,7 @@ public class JSONArray implements Iterable<Object> {
public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this();
if (this.jsonParserConfiguration == null) {
this.jsonParserConfiguration = jsonParserConfiguration;
}
if (this.jsonTokener == null) {
this.jsonTokener = x;
this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration);
}
boolean isInitial = x.getPrevious() == 0;
if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['");
}
@@ -156,11 +143,19 @@ public class JSONArray implements Iterable<Object> {
x.back();
break;
case ']':
if (isInitial && jsonParserConfiguration.isStrictMode() &&
x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return;
default:
throw x.syntaxError("Expected a ',' or ']'");
}
}
} else {
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
}
}
@@ -176,11 +171,6 @@ public class JSONArray implements Iterable<Object> {
*/
public JSONArray(String source) throws JSONException {
this(source, new JSONParserConfiguration());
// Strict mode does not allow trailing chars
if (this.jsonParserConfiguration.isStrictMode() &&
this.jsonTokener.nextClean() != 0) {
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
}
/**
@@ -195,12 +185,7 @@ public class JSONArray implements Iterable<Object> {
* If there is a syntax error.
*/
public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source), jsonParserConfiguration);
// Strict mode does not allow trailing chars
if (this.jsonParserConfiguration.isStrictMode() &&
this.jsonTokener.nextClean() != 0) {
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration);
}
/**

View File

@@ -152,12 +152,6 @@ public class JSONObject {
*/
public static final Object NULL = new Null();
// strict mode checks after constructor require access to this object
private JSONTokener jsonTokener;
// strict mode checks after constructor require access to this object
private JSONParserConfiguration jsonParserConfiguration;
/**
* Construct an empty JSONObject.
*/
@@ -201,7 +195,7 @@ public class JSONObject {
* duplicated key.
*/
public JSONObject(JSONTokener x) throws JSONException {
this(x, new JSONParserConfiguration());
this(x, x.getJsonParserConfiguration());
}
/**
@@ -217,18 +211,11 @@ public class JSONObject {
*/
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this();
if (this.jsonParserConfiguration == null) {
this.jsonParserConfiguration = jsonParserConfiguration;
}
if (this.jsonTokener == null) {
this.jsonTokener = x;
this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration);
}
char c;
String key;
boolean isInitial = x.getPrevious() == 0;
if (x.nextClean() != '{') {
throw x.syntaxError("A JSONObject text must begin with '{'");
}
@@ -238,6 +225,9 @@ public class JSONObject {
case 0:
throw x.syntaxError("A JSONObject text must end with '}'");
case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return;
default:
key = x.nextSimpleValue(c).toString();
@@ -288,6 +278,9 @@ public class JSONObject {
x.back();
break;
case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return;
default:
throw x.syntaxError("Expected a ',' or '}'");
@@ -339,7 +332,7 @@ public class JSONObject {
throw new NullPointerException("Null key.");
}
final Object value = e.getValue();
if (value != null) {
if (value != null || jsonParserConfiguration.isUseNativeNulls()) {
testValidity(value);
this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration));
}
@@ -456,11 +449,6 @@ public class JSONObject {
*/
public JSONObject(String source) throws JSONException {
this(source, new JSONParserConfiguration());
// Strict mode does not allow trailing chars
if (this.jsonParserConfiguration.isStrictMode() &&
this.jsonTokener.nextClean() != 0) {
throw new JSONException("Strict mode error: Unparsed characters found at end of input text");
}
}
/**
@@ -478,12 +466,7 @@ public class JSONObject {
* duplicated key.
*/
public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source), jsonParserConfiguration);
// Strict mode does not allow trailing chars
if (this.jsonParserConfiguration.isStrictMode() &&
this.jsonTokener.nextClean() != 0) {
throw new JSONException("Strict mode error: Unparsed characters found at end of input text");
}
this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration);
}
/**
@@ -1280,7 +1263,7 @@ public class JSONObject {
static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
return objectToBigDecimal(val, defaultValue, true);
}
/**
* @param val value to convert
* @param defaultValue default value to return is the conversion doesn't work or is null.

View File

@@ -8,6 +8,11 @@ public class JSONParserConfiguration extends ParserConfiguration {
* Used to indicate whether to overwrite duplicate key or not.
*/
private boolean overwriteDuplicateKey;
/**
* Used to indicate whether to convert java null values to JSONObject.NULL or ignoring the entry when converting java maps.
*/
private boolean useNativeNulls;
/**
* Configuration with the default values.
@@ -15,6 +20,8 @@ public class JSONParserConfiguration extends ParserConfiguration {
public JSONParserConfiguration() {
super();
this.overwriteDuplicateKey = false;
// DO NOT DELETE THE FOLLOWING LINE -- it is used for strictMode testing
// this.strictMode = true;
}
/**
@@ -27,7 +34,10 @@ public class JSONParserConfiguration extends ParserConfiguration {
protected JSONParserConfiguration clone() {
JSONParserConfiguration clone = new JSONParserConfiguration();
clone.overwriteDuplicateKey = overwriteDuplicateKey;
clone.strictMode = strictMode;
clone.maxNestingDepth = maxNestingDepth;
clone.keepStrings = keepStrings;
clone.useNativeNulls = useNativeNulls;
return clone;
}
@@ -63,6 +73,21 @@ public class JSONParserConfiguration extends ParserConfiguration {
return clone;
}
/**
* Controls the parser's behavior when meeting Java null values while converting maps.
* If set to true, the parser will put a JSONObject.NULL into the resulting JSONObject.
* Or the map entry will be ignored.
*
* @param useNativeNulls defines if the parser should convert null values in Java maps
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public JSONParserConfiguration withUseNativeNulls(final boolean useNativeNulls) {
JSONParserConfiguration clone = this.clone();
clone.useNativeNulls = useNativeNulls;
return clone;
}
/**
* Sets the strict mode configuration for the JSON parser with default true value
@@ -102,8 +127,23 @@ public class JSONParserConfiguration extends ParserConfiguration {
public boolean isOverwriteDuplicateKey() {
return this.overwriteDuplicateKey;
}
/**
* The parser's behavior when meeting a null value in a java map, controls whether the parser should
* write a JSON entry with a null value (<code>isUseNativeNulls() == true</code>)
* or ignore that map entry (<code>isUseNativeNulls() == false</code>).
*
* @return The <code>useNativeNulls</code> configuration value.
*/
public boolean isUseNativeNulls() {
return this.useNativeNulls;
}
/**
* The parser throws an Exception when strict mode is true and tries to parse invalid JSON characters.
* Otherwise, the parser is more relaxed and might tolerate some invalid characters.
*
* @return the current strict mode setting.
*/
public boolean isStrictMode() {

View File

@@ -38,9 +38,21 @@ public class JSONTokener {
/**
* Construct a JSONTokener from a Reader. The caller must close the Reader.
*
* @param reader A reader.
* @param reader the source.
*/
public JSONTokener(Reader reader) {
this(reader, new JSONParserConfiguration());
}
/**
* Construct a JSONTokener from a Reader with a given JSONParserConfiguration. The caller must close the Reader.
*
* @param reader the source.
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
*
*/
public JSONTokener(Reader reader, JSONParserConfiguration jsonParserConfiguration) {
this.jsonParserConfiguration = jsonParserConfiguration;
this.reader = reader.markSupported()
? reader
: new BufferedReader(reader);
@@ -53,23 +65,40 @@ public class JSONTokener {
this.line = 1;
}
/**
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param inputStream The source.
*/
public JSONTokener(InputStream inputStream) {
this(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
this(inputStream, new JSONParserConfiguration());
}
/**
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param inputStream The source.
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
*/
public JSONTokener(InputStream inputStream, JSONParserConfiguration jsonParserConfiguration) {
this(new InputStreamReader(inputStream, Charset.forName("UTF-8")), jsonParserConfiguration);
}
/**
* Construct a JSONTokener from a string.
*
* @param s A source string.
* @param source A source string.
*/
public JSONTokener(String s) {
this(new StringReader(s));
public JSONTokener(String source) {
this(new StringReader(source));
}
/**
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param source The source.
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
*/
public JSONTokener(String source, JSONParserConfiguration jsonParserConfiguration) {
this(new StringReader(source), jsonParserConfiguration);
}
/**
@@ -83,7 +112,10 @@ public class JSONTokener {
/**
* Setter
* @param jsonParserConfiguration new value for jsonParserConfiguration
*
* @deprecated method should not be used
*/
@Deprecated
public void setJsonParserConfiguration(JSONParserConfiguration jsonParserConfiguration) {
this.jsonParserConfiguration = jsonParserConfiguration;
}

View File

@@ -355,10 +355,20 @@ public class XML {
&& TYPE_ATTR.equals(string)) {
xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
} else if (!nilAttributeFound) {
jsonObject.accumulate(string,
config.isKeepStrings()
? ((String) token)
: stringToValue((String) token));
Object obj = stringToValue((String) token);
if (obj instanceof Boolean) {
jsonObject.accumulate(string,
config.isKeepBooleanAsString()
? ((String) token)
: obj);
} else if (obj instanceof Number) {
jsonObject.accumulate(string,
config.isKeepNumberAsString()
? ((String) token)
: obj);
} else {
jsonObject.accumulate(string, stringToValue((String) token));
}
}
token = null;
} else {
@@ -407,8 +417,20 @@ public class XML {
jsonObject.accumulate(config.getcDataTagName(),
stringToValue(string, xmlXsiTypeConverter));
} else {
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepStrings() ? string : stringToValue(string));
Object obj = stringToValue((String) token);
if (obj instanceof Boolean) {
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepBooleanAsString()
? ((String) token)
: obj);
} else if (obj instanceof Number) {
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepNumberAsString()
? ((String) token)
: obj);
} else {
jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token));
}
}
}
@@ -688,6 +710,44 @@ public class XML {
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
}
/**
* Convert a well-formed (but not necessarily valid) XML into a
* JSONObject. Some information may be lost in this transformation because
* JSON is a data format and XML is a document format. XML uses elements,
* attributes, and content text, while JSON uses unordered collections of
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
* "content" member. Comments, prologs, DTDs, and <pre>{@code
* &lt;[ [ ]]>}</pre>
* are ignored.
*
* All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
* numbers but will instead be the exact value as seen in the XML document depending
* on how flag is set.
* All booleans are converted as strings, for true, false will not be coerced to
* booleans but will instead be the exact value as seen in the XML document depending
* on how flag is set.
*
* @param reader The XML source reader.
* @param keepNumberAsString If true, then numeric values will not be coerced into
* numeric values and will instead be left as strings
* @param keepBooleanAsString If true, then boolean values will not be coerced into
* * numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(Reader reader, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
XMLParserConfiguration xmlParserConfiguration = new XMLParserConfiguration();
if(keepNumberAsString) {
xmlParserConfiguration = xmlParserConfiguration.withKeepNumberAsString(keepNumberAsString);
}
if(keepBooleanAsString) {
xmlParserConfiguration = xmlParserConfiguration.withKeepBooleanAsString(keepBooleanAsString);
}
return toJSONObject(reader, xmlParserConfiguration);
}
/**
* Convert a well-formed (but not necessarily valid) XML into a
* JSONObject. Some information may be lost in this transformation because
@@ -746,6 +806,38 @@ public class XML {
return toJSONObject(new StringReader(string), keepStrings);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject. Some information may be lost in this transformation because
* JSON is a data format and XML is a document format. XML uses elements,
* attributes, and content text, while JSON uses unordered collections of
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
* "content" member. Comments, prologs, DTDs, and <pre>{@code
* &lt;[ [ ]]>}</pre>
* are ignored.
*
* All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
* numbers but will instead be the exact value as seen in the XML document depending
* on how flag is set.
* All booleans are converted as strings, for true, false will not be coerced to
* booleans but will instead be the exact value as seen in the XML document depending
* on how flag is set.
*
* @param string
* The source string.
* @param keepNumberAsString If true, then numeric values will not be coerced into
* numeric values and will instead be left as strings
* @param keepBooleanAsString If true, then boolean values will not be coerced into
* numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(String string, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
return toJSONObject(new StringReader(string), keepNumberAsString, keepBooleanAsString);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject. Some information may be lost in this transformation because

View File

@@ -22,6 +22,16 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
// public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512; // We could override
/**
* Allow user to control how numbers are parsed
*/
private boolean keepNumberAsString;
/**
* Allow user to control how booleans are parsed
*/
private boolean keepBooleanAsString;
/** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL
= new XMLParserConfiguration();
@@ -142,7 +152,9 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
super(false, DEFAULT_MAXIMUM_NESTING_DEPTH);
this.keepNumberAsString = keepStrings;
this.keepBooleanAsString = keepStrings;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
}
@@ -163,8 +175,10 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList,
final int maxNestingDepth, final boolean closeEmptyTag) {
super(keepStrings, maxNestingDepth);
final int maxNestingDepth, final boolean closeEmptyTag, final boolean keepNumberAsString, final boolean keepBooleanAsString) {
super(false, maxNestingDepth);
this.keepNumberAsString = keepNumberAsString;
this.keepBooleanAsString = keepBooleanAsString;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
@@ -189,7 +203,9 @@ public class XMLParserConfiguration extends ParserConfiguration {
this.xsiTypeMap,
this.forceList,
this.maxNestingDepth,
this.closeEmptyTag
this.closeEmptyTag,
this.keepNumberAsString,
this.keepBooleanAsString
);
config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
return config;
@@ -207,7 +223,43 @@ public class XMLParserConfiguration extends ParserConfiguration {
@SuppressWarnings("unchecked")
@Override
public XMLParserConfiguration withKeepStrings(final boolean newVal) {
return super.withKeepStrings(newVal);
XMLParserConfiguration newConfig = this.clone();
newConfig.keepStrings = newVal;
newConfig.keepNumberAsString = newVal;
newConfig.keepBooleanAsString = newVal;
return newConfig;
}
/**
* When parsing the XML into JSON, specifies if numbers should be kept as strings (<code>1</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @param newVal
* new value to use for the <code>keepNumberAsString</code> configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withKeepNumberAsString(final boolean newVal) {
XMLParserConfiguration newConfig = this.clone();
newConfig.keepNumberAsString = newVal;
newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
return newConfig;
}
/**
* When parsing the XML into JSON, specifies if booleans should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
*
* @param newVal
* new value to use for the <code>withKeepBooleanAsString</code> configuration option.
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withKeepBooleanAsString(final boolean newVal) {
XMLParserConfiguration newConfig = this.clone();
newConfig.keepBooleanAsString = newVal;
newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
return newConfig;
}
/**
@@ -221,6 +273,26 @@ public class XMLParserConfiguration extends ParserConfiguration {
return this.cDataTagName;
}
/**
* When parsing the XML into JSONML, specifies if numbers should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string).
*
* @return The <code>keepStrings</code> configuration value.
*/
public boolean isKeepNumberAsString() {
return this.keepNumberAsString;
}
/**
* When parsing the XML into JSONML, specifies if booleans should be kept as strings (<code>true</code>), or if
* they should try to be guessed into JSON values (numeric, boolean, string).
*
* @return The <code>keepStrings</code> configuration value.
*/
public boolean isKeepBooleanAsString() {
return this.keepBooleanAsString;
}
/**
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA

View File

@@ -168,6 +168,33 @@ public class CDLTest {
}
}
/**
* Csv parsing skip last row if last field of this row is empty #943
*/
@Test
public void csvParsingCatchesLastRow(){
String data = "Field 1,Field 2,Field 3\n" +
"value11,value12,\n" +
"value21,value22,";
JSONArray jsonArray = CDL.toJSONArray(data);
JSONArray expectedJsonArray = new JSONArray();
JSONObject jsonObject = new JSONObject();
jsonObject.put("Field 1", "value11");
jsonObject.put("Field 2", "value12");
jsonObject.put("Field 3", "");
expectedJsonArray.put(jsonObject);
jsonObject = new JSONObject();
jsonObject.put("Field 1", "value21");
jsonObject.put("Field 2", "value22");
jsonObject.put("Field 3", "");
expectedJsonArray.put(jsonObject);
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
/**
* Assert that there is no error for a single escaped quote within a properly embedded quote.
*/

View File

@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -227,6 +228,19 @@ public class JSONArrayTest {
Util.checkJSONArrayMaps(jaRaw);
Util.checkJSONArrayMaps(jaInt);
}
@Test
public void jsonArrayByListWithNestedNullValue() {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> sub = new HashMap<String, Object>();
sub.put("nullKey", null);
list.add(sub);
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
JSONArray jsonArray = new JSONArray(list, parserConfiguration);
JSONObject subObject = jsonArray.getJSONObject(0);
assertTrue(subObject.has("nullKey"));
assertEquals(JSONObject.NULL, subObject.get("nullKey"));
}
/**
* Tests consecutive calls to putAll with array and collection.
@@ -476,13 +490,18 @@ public class JSONArrayTest {
*/
@Test
public void unquotedText() {
String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
List<Object> expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
// Test should fail if default strictMode is true, pass if false
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
System.out.println("Skipping JSONArrayTest unquotedText() when strictMode default is true");
} else {
String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
try {
JSONArray jsonArray = new JSONArray(str);
List<Object> expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else {
JSONArray jsonArray = new JSONArray(str);
assertEquals(expected, jsonArray.toList());
}
}
@@ -1509,6 +1528,14 @@ public class JSONArrayTest {
new JSONArray(array);
}
@Test
public void testStrictModeJSONTokener_expectException(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
JSONTokener tokener = new JSONTokener("[\"value\"]invalidCharacters", jsonParserConfiguration);
assertThrows(JSONException.class, () -> { new JSONArray(tokener); });
}
public static ArrayList<Object> buildNestedArray(int maxDepth) {
if (maxDepth <= 0) {
return new ArrayList<>();

View File

@@ -218,11 +218,16 @@ public class JSONObjectTest {
*/
@Test
public void unquotedText() {
String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
// Test should fail if default strictMode is true, pass if false
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
System.out.println("Skipping JSONObjectTest unquotedText() when strictMode default is true");
try {
JSONObject jsonObject = new JSONObject(str);
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else {
String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
JSONObject jsonObject = new JSONObject(str);
String textStr = jsonObject.toString();
assertTrue("expected key1", textStr.contains("\"key1\""));
@@ -611,6 +616,46 @@ public class JSONObjectTest {
assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
Util.checkJSONObjectMaps(jsonObject);
}
@Test
public void jsonObjectByMapWithNullValueAndParserConfiguration() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("nullKey", null);
// by default, null values are ignored
JSONObject obj1 = new JSONObject(map);
assertTrue("expected null value to be ignored by default", obj1.isEmpty());
// if configured, null values are written as such into the JSONObject.
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
JSONObject obj2 = new JSONObject(map, parserConfiguration);
assertFalse("expected null value to accepted when configured", obj2.isEmpty());
assertTrue(obj2.has("nullKey"));
assertEquals(JSONObject.NULL, obj2.get("nullKey"));
}
@Test
public void jsonObjectByMapWithNestedNullValueAndParserConfiguration() {
Map<String, Object> map = new HashMap<String, Object>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("nullKey", null);
map.put("nestedMap", nestedMap);
List<Map<String, Object>> nestedList = new ArrayList<Map<String,Object>>();
nestedList.add(nestedMap);
map.put("nestedList", nestedList);
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
JSONObject jsonObject = new JSONObject(map, parserConfiguration);
JSONObject nestedObject = jsonObject.getJSONObject("nestedMap");
assertTrue(nestedObject.has("nullKey"));
assertEquals(JSONObject.NULL, nestedObject.get("nullKey"));
JSONArray nestedArray = jsonObject.getJSONArray("nestedList");
assertEquals(1, nestedArray.length());
assertTrue(nestedArray.getJSONObject(0).has("nullKey"));
assertEquals(JSONObject.NULL, nestedArray.getJSONObject(0).get("nullKey"));
}
/**
* JSONObject built from a bean. In this case all but one of the
@@ -1074,24 +1119,29 @@ public class JSONObjectTest {
*/
@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" +
"}";
// Test should fail if default strictMode is true, pass if false
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
System.out.println("Skipping JSONObjectTest jsonInvalidNumberValues() when strictMode default is true");
try {
JSONObject jsonObject = new JSONObject(str);
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else {
// 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");
@@ -2266,332 +2316,417 @@ public class JSONObjectTest {
}
}
/**
* Explore how JSONObject handles parsing errors.
*/
@SuppressWarnings({"boxing", "unused"})
@Test
public void jsonObjectParsingErrors() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
System.out.println("Skipping JSONObjectTest jaonObjectParsingErrors() when strictMode default is true");
} else {
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 {
// key is a nested map
String str = "{{\"foo\": \"bar\"}: \"baz\"}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Missing value at 1 [character 2 line 1]",
e.getMessage());
}
try {
// key is a nested array containing a map
String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Missing value at 9 [character 10 line 1]",
e.getMessage());
}
try {
// key contains }
String str = "{foo}: 2}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Expected a ':' after a key at 5 [character 6 line 1]",
e.getMessage());
}
try {
// key contains ]
String str = "{foo]: 2}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Expected a ':' after a key at 5 [character 6 line 1]",
e.getMessage());
}
try {
// \0 after ,
String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"A JSONObject text must end with '}' 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());
}
public void parsingErrorTrailingCurlyBrace () {
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 {
// 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());
}
@Test
public void parsingErrorInitialCurlyBrace() {
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());
}
}
@Test
public void parsingErrorNoColon() {
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());
}
}
@Test
public void parsingErrorNoCommaSeparator() {
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());
}
}
@Test
public void parsingErrorKeyIsNestedMap() {
try {
// key is a nested map
String str = "{{\"foo\": \"bar\"}: \"baz\"}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Missing value at 1 [character 2 line 1]",
e.getMessage());
}
}
@Test
public void parsingErrorKeyIsNestedArrayWithMap() {
try {
// key is a nested array containing a map
String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Missing value at 9 [character 10 line 1]",
e.getMessage());
}
}
@Test
public void parsingErrorKeyContainsCurlyBrace() {
try {
// key contains }
String str = "{foo}: 2}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
// assertEquals("Expecting an exception message",
// "Expected a ':' after a key at 5 [character 6 line 1]",
// e.getMessage());
}
}
@Test
public void parsingErrorKeyContainsSquareBrace() {
try {
// key contains ]
String str = "{foo]: 2}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
// assertEquals("Expecting an exception message",
// "Expected a ':' after a key at 5 [character 6 line 1]",
// e.getMessage());
}
}
@Test
public void parsingErrorKeyContainsBinaryZero() {
try {
// \0 after ,
String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
assertNull("Expected an exception", new JSONObject(str));
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"A JSONObject text must end with '}' at 15 [character 16 line 1]",
e.getMessage());
}
}
@Test
public void parsingErrorAppendToWrongValue() {
try {
// append to wrong value
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());
}
}
@Test
public void parsingErrorIncrementWrongValue() {
try {
// increment wrong value
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());
}
}
@Test
public void parsingErrorInvalidKey() {
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());
}
}
@Test
public void parsingErrorNumberToString() {
try {
// invalid numberToString()
JSONObject.numberToString((Number) null);
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
"Null pointer",
e.getMessage());
}
}
@Test
public void parsingErrorPutOnceDuplicateKey() {
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);
}
}
@Test
public void parsingErrorInvalidDouble() {
try {
// test validity of invalid double
JSONObject.testValidity(Double.NaN);
fail("Expected an exception");
} catch (JSONException e) {
assertTrue("", true);
}
}
@Test
public void parsingErrorInvalidFloat() {
try {
// test validity of invalid float
JSONObject.testValidity(Float.NEGATIVE_INFINITY);
fail("Expected an exception");
} catch (JSONException e) {
assertTrue("", true);
}
}
@Test
public void parsingErrorDuplicateKeyException() {
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());
}
}
@Test
public void parsingErrorNestedDuplicateKeyException() {
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());
}
}
@Test
public void parsingErrorNestedDuplicateKeyWithArrayException() {
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());
}
}
@Test
public void parsingErrorDuplicateKeyWithinNestedDictExceptionMessage() {
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());
}
}
@Test
public void parsingErrorDuplicateKeyDoubleNestedDictExceptionMessage() {
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());
}
}
@Test
public void parsingErrorDuplicateKeyNestedWithArrayExceptionMessage() {
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());
}
}
@Test
public void parsingErrorDuplicateKeyWithinArrayExceptionMessage() {
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());
}
}
@Test
public void parsingErrorDuplicateKeyDoubleNestedWithinArrayExceptionMessage() {
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());
}
}
@@ -3853,6 +3988,15 @@ public class JSONObjectTest {
assertEquals(j3.getString("hex6"), "0011");
}
@Test
public void testStrictModeJSONTokener_expectException(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
JSONTokener tokener = new JSONTokener("{\"key\":\"value\"}invalidCharacters", jsonParserConfiguration);
assertThrows(JSONException.class, () -> { new JSONObject(tokener); });
}
/**
* Method to build nested map of max maxDepth
*

View File

@@ -4,6 +4,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONParserConfiguration;
import org.json.JSONTokener;
import org.junit.Test;
import java.io.IOException;
@@ -14,7 +15,10 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class JSONParserConfigurationTest {
private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
@@ -32,6 +36,32 @@ public class JSONParserConfigurationTest {
assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key"));
}
@Test
public void strictModeIsCloned(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true)
.withMaxNestingDepth(12);
assertTrue(jsonParserConfiguration.isStrictMode());
}
@Test
public void maxNestingDepthIsCloned(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.<JSONParserConfiguration>withKeepStrings(true)
.withStrictMode(true);
assertTrue(jsonParserConfiguration.isKeepStrings());
}
@Test
public void useNativeNullsIsCloned() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withUseNativeNulls(true)
.withStrictMode(true);
assertTrue(jsonParserConfiguration.isUseNativeNulls());
}
@Test
public void verifyDuplicateKeyThenMaxDepth() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
@@ -490,6 +520,40 @@ public class JSONParserConfigurationTest {
je.getMessage());
}
@Test
public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingJSONTokener_shouldThrowJSONException() {
JSONException exception = assertThrows(JSONException.class, () -> {
new JSONObject(new JSONTokener("{\"key\":\"value\"} invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
});
assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
}
@Test
public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingString_shouldThrowJSONException() {
JSONException exception = assertThrows(JSONException.class, () -> {
new JSONObject("{\"key\":\"value\"} invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
});
assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
}
@Test
public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingJSONTokener_shouldThrowJSONException() {
JSONException exception = assertThrows(JSONException.class, () -> {
new JSONArray(new JSONTokener("[\"value\"] invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
});
assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
}
@Test
public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingString_shouldThrowJSONException() {
JSONException exception = assertThrows(JSONException.class, () -> {
new JSONArray("[\"value\"] invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
});
assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
}
/**
* This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
* this class.

View File

@@ -16,10 +16,7 @@ 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.json.*;
import org.junit.Test;
/**
@@ -98,7 +95,17 @@ public class JSONTokenerTest {
checkValid(" [] ",JSONArray.class);
checkValid("[1,2]",JSONArray.class);
checkValid("\n\n[1,2]\n\n",JSONArray.class);
checkValid("1 2", String.class);
// Test should fail if default strictMode is true, pass if false
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
try {
checkValid("1 2", String.class);
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else {
checkValid("1 2", String.class);
}
}
@Test
@@ -325,4 +332,42 @@ public class JSONTokenerTest {
assertEquals("Stream closed", exception.getMessage());
}
}
@Test
public void testInvalidInput_JSONObject_withoutStrictModel_shouldParseInput() {
String input = "{\"invalidInput\": [],}";
JSONTokener tokener = new JSONTokener(input);
// Test should fail if default strictMode is true, pass if false
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
try {
Object value = tokener.nextValue();
assertEquals(new JSONObject(input).toString(), value.toString());
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else {
Object value = tokener.nextValue();
assertEquals(new JSONObject(input).toString(), value.toString());
}
}
@Test
public void testInvalidInput_JSONArray_withoutStrictModel_shouldParseInput() {
String input = "[\"invalidInput\",]";
JSONTokener tokener = new JSONTokener(input);
// Test should fail if default strictMode is true, pass if false
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) {
try {
Object value = tokener.nextValue();
assertEquals(new JSONArray(input).toString(), value.toString());
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else {
Object value = tokener.nextValue();
assertEquals(new JSONArray(input).toString(), value.toString());
}
}
}

View File

@@ -574,15 +574,18 @@ public class XMLConfigurationTest {
XMLParserConfiguration keepStringsAndCloseEmptyTag = keepStrings.withCloseEmptyTag(true);
XMLParserConfiguration keepDigits = keepStringsAndCloseEmptyTag.withKeepStrings(false);
XMLParserConfiguration keepDigitsAndNoCloseEmptyTag = keepDigits.withCloseEmptyTag(false);
assertTrue(keepStrings.isKeepStrings());
assertTrue(keepStrings.isKeepNumberAsString());
assertTrue(keepStrings.isKeepBooleanAsString());
assertFalse(keepStrings.isCloseEmptyTag());
assertTrue(keepStringsAndCloseEmptyTag.isKeepStrings());
assertTrue(keepStringsAndCloseEmptyTag.isKeepNumberAsString());
assertTrue(keepStringsAndCloseEmptyTag.isKeepBooleanAsString());
assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag());
assertFalse(keepDigits.isKeepStrings());
assertFalse(keepDigits.isKeepNumberAsString());
assertFalse(keepDigits.isKeepBooleanAsString());
assertTrue(keepDigits.isCloseEmptyTag());
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepStrings());
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepNumberAsString());
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepBooleanAsString());
assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag());
}
/**
@@ -767,6 +770,55 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
/**
* JSON string lost leading zero and converted "True" to true.
*/
@Test
public void testToJSONArray_jsonOutput_withKeepNumberAsString() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":true}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepNumberAsString(true));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
/**
* JSON string lost leading zero and converted "True" to true.
*/
@Test
public void testToJSONArray_jsonOutput_withKeepBooleanAsString() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":\"True\"}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepBooleanAsString(true));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
/**
* Test keepStrings behavior when setting keepBooleanAsString, keepNumberAsString
*/
@Test
public void test_keepStringBehavior() {
XMLParserConfiguration xpc = new XMLParserConfiguration().withKeepStrings(true);
assertEquals(xpc.isKeepStrings(), true);
xpc = xpc.withKeepBooleanAsString(true);
xpc = xpc.withKeepNumberAsString(false);
assertEquals(xpc.isKeepStrings(), false);
xpc = xpc.withKeepBooleanAsString(false);
xpc = xpc.withKeepNumberAsString(true);
assertEquals(xpc.isKeepStrings(), false);
xpc = xpc.withKeepBooleanAsString(true);
xpc = xpc.withKeepNumberAsString(true);
assertEquals(xpc.isKeepStrings(), true);
xpc = xpc.withKeepBooleanAsString(false);
xpc = xpc.withKeepNumberAsString(false);
assertEquals(xpc.isKeepStrings(), false);
}
/**
* JSON string cannot be reverted to original xml.
*/