Compare commits

..

No commits in common. "master" and "20250107" have entirely different histories.

23 changed files with 620 additions and 1703 deletions

View File

@ -29,7 +29,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -40,4 +40,4 @@ jobs:
- run: "mvn clean compile -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true" - run: "mvn clean compile -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true"
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2

View File

@ -30,7 +30,7 @@ jobs:
jar cvf target/org.json.jar -C target/classes . jar cvf target/org.json.jar -C target/classes .
- name: Upload JAR 1.6 - name: Upload JAR 1.6
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Create java 1.6 JAR name: Create java 1.6 JAR
path: target/*.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 }} mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }} - name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Results ${{ matrix.java }} name: Test Results ${{ matrix.java }}
path: target/surefire-reports/ path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }} - name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Report ${{ matrix.java }} name: Test Report ${{ matrix.java }}
path: target/site/ 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 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 }} - name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Package Jar ${{ matrix.java }} name: Package Jar ${{ matrix.java }}
path: target/*.jar 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 }} mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }} - name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Results ${{ matrix.java }} name: Test Results ${{ matrix.java }}
path: target/surefire-reports/ path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }} - name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Report ${{ matrix.java }} name: Test Report ${{ matrix.java }}
path: target/site/ 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 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 }} - name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Package Jar ${{ matrix.java }} name: Package Jar ${{ matrix.java }}
path: target/*.jar 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 }} mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }} - name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Results ${{ matrix.java }} name: Test Results ${{ matrix.java }}
path: target/surefire-reports/ path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }} - name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Report ${{ matrix.java }} name: Test Report ${{ matrix.java }}
path: target/site/ 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 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 }} - name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Package Jar ${{ matrix.java }} name: Package Jar ${{ matrix.java }}
path: target/*.jar 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 }} mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
- name: Upload Test Results ${{ matrix.java }} - name: Upload Test Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Results ${{ matrix.java }} name: Test Results ${{ matrix.java }}
path: target/surefire-reports/ path: target/surefire-reports/
- name: Upload Test Report ${{ matrix.java }} - name: Upload Test Report ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Test Report ${{ matrix.java }} name: Test Report ${{ matrix.java }}
path: target/site/ 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 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 }} - name: Upload Package Results ${{ matrix.java }}
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: Package Jar ${{ matrix.java }} name: Package Jar ${{ matrix.java }}
path: target/*.jar 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) [![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) [![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/20250517/json-20250517.jar)** **[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20241224/json-20241224.jar)**
# Overview # Overview
@ -97,18 +97,6 @@ Execute the test suite with Gradlew:
gradlew clean build test 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 # Notes
For more information, please see [NOTES.md](https://github.com/stleary/JSON-java/blob/master/docs/NOTES.md) For more information, please see [NOTES.md](https://github.com/stleary/JSON-java/blob/master/docs/NOTES.md)

View File

@ -3,10 +3,9 @@
*/ */
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'jacoco' // apply plugin: 'jacoco'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
// for now, publishing to maven is still a manual process
//plugins { //plugins {
// id 'java' // id 'java'
//id 'maven-publish' //id 'maven-publish'
@ -20,17 +19,6 @@ repositories {
} }
} }
// To view the report open build/reports/jacoco/test/html/index.html
jacocoTestReport {
reports {
html.required = true
}
}
test {
finalizedBy jacocoTestReport
}
dependencies { dependencies {
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'com.jayway.jsonpath:json-path:2.9.0' testImplementation 'com.jayway.jsonpath:json-path:2.9.0'
@ -42,7 +30,7 @@ subprojects {
} }
group = 'org.json' group = 'org.json'
version = 'v20250517-SNAPSHOT' version = 'v20230618-SNAPSHOT'
description = 'JSON in Java' description = 'JSON in Java'
sourceCompatibility = '1.8' sourceCompatibility = '1.8'
@ -65,75 +53,3 @@ publishing {
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.encoding = 'UTF-8' 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,12 +5,7 @@ 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) [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)
~~~ ~~~
20250517 Strict mode hardening and recent commits 20241224....Strict mode opt-in feature, and recent commits.
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. 20240303 Revert optLong/getLong changes, and recent commits.

77
pom.xml
View File

@ -3,7 +3,7 @@
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20250517</version> <version>20241224</version>
<packaging>bundle</packaging> <packaging>bundle</packaging>
<name>JSON in Java</name> <name>JSON in Java</name>
@ -193,6 +193,30 @@
</execution> </execution>
</executions> </executions>
</plugin> </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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
@ -200,55 +224,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </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> </project>

View File

@ -100,15 +100,11 @@ public class CDL {
for (;;) { for (;;) {
String value = getValue(x,delimiter); String value = getValue(x,delimiter);
char c = x.next(); char c = x.next();
if (value != null) { if (value == null ||
ja.put(value); (ja.length() == 0 && value.length() == 0 && c != delimiter)) {
} else if (ja.length() == 0 && c != delimiter) {
return null; return null;
} else {
// This line accounts for CSV ending with no newline
ja.put("");
} }
ja.put(value);
for (;;) { for (;;) {
if (c == delimiter) { if (c == delimiter) {
break; break;
@ -311,17 +307,6 @@ public class CDL {
if (ja.length() == 0) { if (ja.length() == 0) {
return null; 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; return ja;
} }

View File

@ -67,6 +67,12 @@ public class JSONArray implements Iterable<Object> {
*/ */
private final ArrayList<Object> myArrayList; 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. * Construct an empty JSONArray.
*/ */
@ -83,7 +89,7 @@ public class JSONArray implements Iterable<Object> {
* If there is a syntax error. * If there is a syntax error.
*/ */
public JSONArray(JSONTokener x) throws JSONException { public JSONArray(JSONTokener x) throws JSONException {
this(x, x.getJsonParserConfiguration()); this(x, new JSONParserConfiguration());
} }
/** /**
@ -96,7 +102,14 @@ public class JSONArray implements Iterable<Object> {
public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException { public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(); this();
boolean isInitial = x.getPrevious() == 0; if (this.jsonParserConfiguration == null) {
this.jsonParserConfiguration = jsonParserConfiguration;
}
if (this.jsonTokener == null) {
this.jsonTokener = x;
this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration);
}
if (x.nextClean() != '[') { if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['"); throw x.syntaxError("A JSONArray text must start with '['");
} }
@ -143,19 +156,11 @@ public class JSONArray implements Iterable<Object> {
x.back(); x.back();
break; break;
case ']': case ']':
if (isInitial && jsonParserConfiguration.isStrictMode() &&
x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return; return;
default: default:
throw x.syntaxError("Expected a ',' or ']'"); 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");
}
} }
} }
@ -171,6 +176,11 @@ public class JSONArray implements Iterable<Object> {
*/ */
public JSONArray(String source) throws JSONException { public JSONArray(String source) throws JSONException {
this(source, new JSONParserConfiguration()); 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");
}
} }
/** /**
@ -185,7 +195,12 @@ public class JSONArray implements Iterable<Object> {
* If there is a syntax error. * If there is a syntax error.
*/ */
public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException { public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration); 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");
}
} }
/** /**
@ -334,11 +349,13 @@ public class JSONArray implements Iterable<Object> {
*/ */
public boolean getBoolean(int index) throws JSONException { public boolean getBoolean(int index) throws JSONException {
Object object = this.get(index); Object object = this.get(index);
if (Boolean.FALSE.equals(object) if (object.equals(Boolean.FALSE)
|| (object instanceof String && "false".equalsIgnoreCase((String) object))) { || (object instanceof String && ((String) object)
.equalsIgnoreCase("false"))) {
return false; return false;
} else if (Boolean.TRUE.equals(object) } else if (object.equals(Boolean.TRUE)
|| (object instanceof String && "true".equalsIgnoreCase((String) object))) { || (object instanceof String && ((String) object)
.equalsIgnoreCase("true"))) {
return true; return true;
} }
throw wrongValueFormatException(index, "boolean", object, null); throw wrongValueFormatException(index, "boolean", object, null);

View File

@ -111,7 +111,7 @@ public class JSONML {
} }
} else if (c == '[') { } else if (c == '[') {
token = x.nextToken(); token = x.nextToken();
if ("CDATA".equals(token) && x.next() == '[') { if (token.equals("CDATA") && x.next() == '[') {
if (ja != null) { if (ja != null) {
ja.put(x.nextCDATA()); ja.put(x.nextCDATA());
} }

View File

@ -79,6 +79,17 @@ public class JSONObject {
*/ */
private static final class Null { private static final class Null {
/**
* There is only intended to be a single instance of the NULL object,
* so the clone method returns itself.
*
* @return NULL.
*/
@Override
protected final Object clone() {
return this;
}
/** /**
* A Null object is equal to the null value and to itself. * A Null object is equal to the null value and to itself.
* *
@ -141,6 +152,12 @@ public class JSONObject {
*/ */
public static final Object NULL = new Null(); 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. * Construct an empty JSONObject.
*/ */
@ -169,7 +186,7 @@ public class JSONObject {
for (int i = 0; i < names.length; i += 1) { for (int i = 0; i < names.length; i += 1) {
try { try {
this.putOnce(names[i], jo.opt(names[i])); this.putOnce(names[i], jo.opt(names[i]));
} catch (Exception ignore) { // exception thrown for missing key } catch (Exception ignore) {
} }
} }
} }
@ -184,7 +201,7 @@ public class JSONObject {
* duplicated key. * duplicated key.
*/ */
public JSONObject(JSONTokener x) throws JSONException { public JSONObject(JSONTokener x) throws JSONException {
this(x, x.getJsonParserConfiguration()); this(x, new JSONParserConfiguration());
} }
/** /**
@ -200,134 +217,80 @@ public class JSONObject {
*/ */
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException { public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(); this();
boolean isInitial = x.getPrevious() == 0;
if (this.jsonParserConfiguration == null) {
this.jsonParserConfiguration = jsonParserConfiguration;
}
if (this.jsonTokener == null) {
this.jsonTokener = x;
this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration);
}
char c;
String key;
if (x.nextClean() != '{') { if (x.nextClean() != '{') {
throw x.syntaxError("A JSONObject text must begin with '{'"); throw x.syntaxError("A JSONObject text must begin with '{'");
} }
for (;;) { for (;;) {
if (parseJSONObject(x, jsonParserConfiguration, isInitial)) { c = x.nextClean();
return; switch (c) {
}
}
}
/**
* Parses entirety of JSON object
*
* @param jsonTokener Parses text as tokens
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
* @param isInitial True if start of document, else false
* @return True if done building object, else false
*/
private boolean parseJSONObject(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, boolean isInitial) {
Object obj;
String key;
boolean doneParsing = false;
char c = jsonTokener.nextClean();
switch (c) {
case 0: case 0:
throw jsonTokener.syntaxError("A JSONObject text must end with '}'"); throw x.syntaxError("A JSONObject text must end with '}'");
case '}': case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) { return;
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return true;
default: default:
obj = jsonTokener.nextSimpleValue(c); key = x.nextSimpleValue(c).toString();
key = obj.toString();
}
checkKeyForStrictMode(jsonTokener, jsonParserConfiguration, obj);
// The key is followed by ':'.
c = jsonTokener.nextClean();
if (c != ':') {
throw jsonTokener.syntaxError("Expected a ':' after a key");
}
// Use syntaxError(..) to include error location
if (key != null) {
// Check if key exists
boolean keyExists = this.opt(key) != null;
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
throw jsonTokener.syntaxError("Duplicate key \"" + key + "\"");
} }
Object value = jsonTokener.nextValue(); // The key is followed by ':'.
// Only add value if non-null
if (value != null) { c = x.nextClean();
this.put(key, value); if (c != ':') {
throw x.syntaxError("Expected a ':' after a key");
} }
}
// Pairs are separated by ','. // Use syntaxError(..) to include error location
if (parseEndOfKeyValuePair(jsonTokener, jsonParserConfiguration, isInitial)) {
doneParsing = true;
}
return doneParsing; if (key != null) {
} // Check if key exists
boolean keyExists = this.opt(key) != null;
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
throw x.syntaxError("Duplicate key \"" + key + "\"");
}
/** Object value = x.nextValue();
* Checks for valid end of key:value pair // Only add value if non-null
* @param jsonTokener Parses text as tokens if (value != null) {
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing. this.put(key, value);
* @param isInitial True if end of JSON object, else false }
* @return }
*/
private static boolean parseEndOfKeyValuePair(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, boolean isInitial) { // Pairs are separated by ','.
switch (jsonTokener.nextClean()) {
switch (x.nextClean()) {
case ';': case ';':
// In strict mode semicolon is not a valid separator // In strict mode semicolon is not a valid separator
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
throw jsonTokener.syntaxError("Strict mode error: Invalid character ';' found"); throw x.syntaxError("Strict mode error: Invalid character ';' found");
} }
break;
case ',': case ',':
if (jsonTokener.nextClean() == '}') { if (x.nextClean() == '}') {
// trailing commas are not allowed in strict mode // trailing commas are not allowed in strict mode
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
throw jsonTokener.syntaxError("Strict mode error: Expected another object element"); throw x.syntaxError("Strict mode error: Expected another object element");
} }
// End of JSON object return;
return true;
} }
if (jsonTokener.end()) { if (x.end()) {
throw jsonTokener.syntaxError("A JSONObject text must end with '}'"); throw x.syntaxError("A JSONObject text must end with '}'");
} }
jsonTokener.back(); x.back();
break; break;
case '}': case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) { return;
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
// End of JSON object
return true;
default: default:
throw jsonTokener.syntaxError("Expected a ',' or '}'"); throw x.syntaxError("Expected a ',' or '}'");
}
// Not at end of JSON object
return false;
}
/**
* Throws error if key violates strictMode
* @param jsonTokener Parses text as tokens
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
* @param obj Value to be checked
*/
private static void checkKeyForStrictMode(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, Object obj) {
if (jsonParserConfiguration != null && jsonParserConfiguration.isStrictMode()) {
if(obj instanceof Boolean) {
throw jsonTokener.syntaxError(String.format("Strict mode error: key '%s' cannot be boolean", obj.toString()));
}
if(obj == JSONObject.NULL) {
throw jsonTokener.syntaxError(String.format("Strict mode error: key '%s' cannot be null", obj.toString()));
}
if(obj instanceof Number) {
throw jsonTokener.syntaxError(String.format("Strict mode error: key '%s' cannot be number", obj.toString()));
} }
} }
} }
@ -376,7 +339,7 @@ public class JSONObject {
throw new NullPointerException("Null key."); throw new NullPointerException("Null key.");
} }
final Object value = e.getValue(); final Object value = e.getValue();
if (value != null || jsonParserConfiguration.isUseNativeNulls()) { if (value != null) {
testValidity(value); testValidity(value);
this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration)); this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration));
} }
@ -445,17 +408,12 @@ public class JSONObject {
*/ */
public JSONObject(Object bean) { public JSONObject(Object bean) {
this(); this();
this.populateMap(bean, new JSONParserConfiguration()); this.populateMap(bean);
}
public JSONObject(Object bean, JSONParserConfiguration jsonParserConfiguration) {
this();
this.populateMap(bean, jsonParserConfiguration);
} }
private JSONObject(Object bean, Set<Object> objectsRecord) { private JSONObject(Object bean, Set<Object> objectsRecord) {
this(); this();
this.populateMap(bean, objectsRecord, new JSONParserConfiguration()); this.populateMap(bean, objectsRecord);
} }
/** /**
@ -498,6 +456,11 @@ public class JSONObject {
*/ */
public JSONObject(String source) throws JSONException { public JSONObject(String source) throws JSONException {
this(source, new JSONParserConfiguration()); 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");
}
} }
/** /**
@ -515,7 +478,12 @@ public class JSONObject {
* duplicated key. * duplicated key.
*/ */
public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException { public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration); 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");
}
} }
/** /**
@ -723,11 +691,13 @@ public class JSONObject {
*/ */
public boolean getBoolean(String key) throws JSONException { public boolean getBoolean(String key) throws JSONException {
Object object = this.get(key); Object object = this.get(key);
if (Boolean.FALSE.equals(object) if (object.equals(Boolean.FALSE)
|| (object instanceof String && "false".equalsIgnoreCase((String) object))) { || (object instanceof String && ((String) object)
.equalsIgnoreCase("false"))) {
return false; return false;
} else if (Boolean.TRUE.equals(object) } else if (object.equals(Boolean.TRUE)
|| (object instanceof String && "true".equalsIgnoreCase((String) object))) { || (object instanceof String && ((String) object)
.equalsIgnoreCase("true"))) {
return true; return true;
} }
throw wrongValueFormatException(key, "Boolean", object, null); throw wrongValueFormatException(key, "Boolean", object, null);
@ -1811,79 +1781,62 @@ public class JSONObject {
* @throws JSONException * @throws JSONException
* If a getter returned a non-finite number. * If a getter returned a non-finite number.
*/ */
private void populateMap(Object bean, JSONParserConfiguration jsonParserConfiguration) { private void populateMap(Object bean) {
populateMap(bean, Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()), jsonParserConfiguration); populateMap(bean, Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()));
} }
/** private void populateMap(Object bean, Set<Object> objectsRecord) {
* Convert a bean into a json object
* @param bean object tobe converted
* @param objectsRecord set of all objects for this method
* @param jsonParserConfiguration json parser settings
*/
private void populateMap(Object bean, Set<Object> objectsRecord, JSONParserConfiguration jsonParserConfiguration) {
Class<?> klass = bean.getClass(); Class<?> klass = bean.getClass();
// If klass is a System class then set includeSuperClass to false. // If klass is a System class then set includeSuperClass to false.
Method[] methods = getMethods(klass);
for (final Method method : methods) {
if (isValidMethod(method)) {
final String key = getKeyNameFromMethod(method);
if (key != null && !key.isEmpty()) {
processMethod(bean, objectsRecord, jsonParserConfiguration, method, key);
}
}
}
}
/**
* Processes method into json object entry if appropriate
* @param bean object being processed (owns the method)
* @param objectsRecord set of all objects for this method
* @param jsonParserConfiguration json parser settings
* @param method method being processed
* @param key name of the method
*/
private void processMethod(Object bean, Set<Object> objectsRecord, JSONParserConfiguration jsonParserConfiguration,
Method method, String key) {
try {
final Object result = method.invoke(bean);
if (result != null || jsonParserConfiguration.isUseNativeNulls()) {
// check cyclic dependency and throw error if needed
// the wrap and populateMap combination method is
// itself DFS recursive
if (objectsRecord.contains(result)) {
throw recursivelyDefinedObjectException(key);
}
objectsRecord.add(result);
testValidity(result);
this.map.put(key, wrap(result, objectsRecord));
objectsRecord.remove(result);
closeClosable(result);
}
} catch (IllegalAccessException ignore) {
// ignore exception
} catch (IllegalArgumentException ignore) {
// ignore exception
} catch (InvocationTargetException ignore) {
// ignore exception
}
}
/**
* This is a convenience method to simplify populate maps
* @param klass the name of the object being checked
* @return methods of klass
*/
private static Method[] getMethods(Class<?> klass) {
boolean includeSuperClass = klass.getClassLoader() != null; boolean includeSuperClass = klass.getClassLoader() != null;
return includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();
for (final Method method : methods) {
final int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers)
&& !Modifier.isStatic(modifiers)
&& method.getParameterTypes().length == 0
&& !method.isBridge()
&& method.getReturnType() != Void.TYPE
&& isValidMethodName(method.getName())) {
final String key = getKeyNameFromMethod(method);
if (key != null && !key.isEmpty()) {
try {
final Object result = method.invoke(bean);
if (result != null) {
// check cyclic dependency and throw error if needed
// the wrap and populateMap combination method is
// itself DFS recursive
if (objectsRecord.contains(result)) {
throw recursivelyDefinedObjectException(key);
}
objectsRecord.add(result);
testValidity(result);
this.map.put(key, wrap(result, objectsRecord));
objectsRecord.remove(result);
// we don't use the result anywhere outside of wrap
// if it's a resource we should be sure to close it
// after calling toString
if (result instanceof Closeable) {
try {
((Closeable) result).close();
} catch (IOException ignore) {
}
}
}
} catch (IllegalAccessException ignore) {
} catch (IllegalArgumentException ignore) {
} catch (InvocationTargetException ignore) {
}
}
}
}
} }
private static boolean isValidMethodName(String name) { private static boolean isValidMethodName(String name) {
@ -1901,7 +1854,7 @@ public class JSONObject {
} }
} }
JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class); JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class);
if (annotationValueNotEmpty(annotation)) { if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
return annotation.value(); return annotation.value();
} }
String key; String key;
@ -1927,46 +1880,6 @@ public class JSONObject {
return key; return key;
} }
/**
* checks if the annotation is not null and the {@link JSONPropertyName#value()} is not null and is not empty.
* @param annotation the annotation to check
* @return true if the annotation and the value is not null and not empty, false otherwise.
*/
private static boolean annotationValueNotEmpty(JSONPropertyName annotation) {
return annotation != null && annotation.value() != null && !annotation.value().isEmpty();
}
/**
* Checks if the method is valid for the {@link #populateMap(Object, Set, JSONParserConfiguration)} use case
* @param method the Method to check
* @return true, if valid, false otherwise.
*/
private static boolean isValidMethod(Method method) {
final int modifiers = method.getModifiers();
return Modifier.isPublic(modifiers)
&& !Modifier.isStatic(modifiers)
&& method.getParameterTypes().length == 0
&& !method.isBridge()
&& method.getReturnType() != Void.TYPE
&& isValidMethodName(method.getName());
}
/**
* calls {@link Closeable#close()} on the input, if it is an instance of Closable.
* @param input the input to close, if possible.
*/
private static void closeClosable(Object input) {
// we don't use the result anywhere outside of wrap
// if it's a resource we should be sure to close it
// after calling toString
if (input instanceof Closeable) {
try {
((Closeable) input).close();
} catch (IOException ignore) {
}
}
}
/** /**
* Searches the class hierarchy to see if the method or it's super * Searches the class hierarchy to see if the method or it's super
* implementations and interfaces has the annotation. * implementations and interfaces has the annotation.
@ -2010,7 +1923,7 @@ public class JSONObject {
} }
//If the superclass is Object, no annotations will be found any more //If the superclass is Object, no annotations will be found any more
if (Object.class.equals(c.getSuperclass())) if (c.getSuperclass().equals(Object.class))
return null; return null;
try { try {
@ -2068,7 +1981,7 @@ public class JSONObject {
} }
//If the superclass is Object, no annotations will be found any more //If the superclass is Object, no annotations will be found any more
if (Object.class.equals(c.getSuperclass())) if (c.getSuperclass().equals(Object.class))
return -1; return -1;
try { try {
@ -2854,13 +2767,13 @@ public class JSONObject {
return NULL; return NULL;
} }
if (object instanceof JSONObject || object instanceof JSONArray if (object instanceof JSONObject || object instanceof JSONArray
|| object instanceof JSONString || object instanceof String || NULL.equals(object) || object instanceof JSONString
|| object instanceof Byte || object instanceof Character || object instanceof Byte || object instanceof Character
|| object instanceof Short || object instanceof Integer || object instanceof Short || object instanceof Integer
|| object instanceof Long || object instanceof Boolean || object instanceof Long || object instanceof Boolean
|| object instanceof Float || object instanceof Double || object instanceof Float || object instanceof Double
|| object instanceof BigInteger || object instanceof BigDecimal || object instanceof String || object instanceof BigInteger
|| object instanceof Enum) { || object instanceof BigDecimal || object instanceof Enum) {
return object; return object;
} }
@ -3114,4 +3027,24 @@ public class JSONObject {
"JavaBean object contains recursively defined member variable of key " + quote(key) "JavaBean object contains recursively defined member variable of key " + quote(key)
); );
} }
/**
* For a prospective number, remove the leading zeros
* @param value prospective number
* @return number without leading zeros
*/
private static String removeLeadingZerosOfNumber(String value){
if (value.equals("-")){return value;}
boolean negativeFirstChar = (value.charAt(0) == '-');
int counter = negativeFirstChar ? 1:0;
while (counter < value.length()){
if (value.charAt(counter) != '0'){
if (negativeFirstChar) {return "-".concat(value.substring(counter));}
return value.substring(counter);
}
++counter;
}
if (negativeFirstChar) {return "-0";}
return "0";
}
} }

View File

@ -9,19 +9,12 @@ public class JSONParserConfiguration extends ParserConfiguration {
*/ */
private boolean overwriteDuplicateKey; 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. * Configuration with the default values.
*/ */
public JSONParserConfiguration() { public JSONParserConfiguration() {
super(); super();
this.overwriteDuplicateKey = false; this.overwriteDuplicateKey = false;
// DO NOT DELETE THE FOLLOWING LINE -- it is used for strictMode testing
// this.strictMode = true;
} }
/** /**
@ -34,10 +27,7 @@ public class JSONParserConfiguration extends ParserConfiguration {
protected JSONParserConfiguration clone() { protected JSONParserConfiguration clone() {
JSONParserConfiguration clone = new JSONParserConfiguration(); JSONParserConfiguration clone = new JSONParserConfiguration();
clone.overwriteDuplicateKey = overwriteDuplicateKey; clone.overwriteDuplicateKey = overwriteDuplicateKey;
clone.strictMode = strictMode;
clone.maxNestingDepth = maxNestingDepth; clone.maxNestingDepth = maxNestingDepth;
clone.keepStrings = keepStrings;
clone.useNativeNulls = useNativeNulls;
return clone; return clone;
} }
@ -74,21 +64,6 @@ public class JSONParserConfiguration extends ParserConfiguration {
return clone; 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 * Sets the strict mode configuration for the JSON parser with default true value
* <p> * <p>
@ -129,21 +104,6 @@ public class JSONParserConfiguration extends ParserConfiguration {
} }
/** /**
* 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. * @return the current strict mode setting.
*/ */
public boolean isStrictMode() { public boolean isStrictMode() {

View File

@ -127,7 +127,7 @@ public class JSONPointer {
if (pointer == null) { if (pointer == null) {
throw new NullPointerException("pointer cannot be null"); throw new NullPointerException("pointer cannot be null");
} }
if (pointer.isEmpty() || "#".equals(pointer)) { if (pointer.isEmpty() || pointer.equals("#")) {
this.refTokens = Collections.emptyList(); this.refTokens = Collections.emptyList();
return; return;
} }
@ -246,7 +246,7 @@ public class JSONPointer {
*/ */
@Override @Override
public String toString() { public String toString() {
StringBuilder rval = new StringBuilder(); StringBuilder rval = new StringBuilder("");
for (String token: this.refTokens) { for (String token: this.refTokens) {
rval.append('/').append(escape(token)); rval.append('/').append(escape(token));
} }

View File

@ -38,21 +38,9 @@ public class JSONTokener {
/** /**
* Construct a JSONTokener from a Reader. The caller must close the Reader. * Construct a JSONTokener from a Reader. The caller must close the Reader.
* *
* @param reader the source. * @param reader A reader.
*/ */
public JSONTokener(Reader reader) { 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() this.reader = reader.markSupported()
? reader ? reader
: new BufferedReader(reader); : new BufferedReader(reader);
@ -65,40 +53,23 @@ public class JSONTokener {
this.line = 1; this.line = 1;
} }
/** /**
* Construct a JSONTokener from an InputStream. The caller must close the input stream. * Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param inputStream The source. * @param inputStream The source.
*/ */
public JSONTokener(InputStream inputStream) { public JSONTokener(InputStream inputStream) {
this(inputStream, new JSONParserConfiguration()); this(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
}
/**
* 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. * Construct a JSONTokener from a string.
* *
* @param source A source string. * @param s A source string.
*/ */
public JSONTokener(String source) { public JSONTokener(String s) {
this(new StringReader(source)); this(new StringReader(s));
}
/**
* 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);
} }
/** /**
@ -112,10 +83,7 @@ public class JSONTokener {
/** /**
* Setter * Setter
* @param jsonParserConfiguration new value for jsonParserConfiguration * @param jsonParserConfiguration new value for jsonParserConfiguration
*
* @deprecated method should not be used
*/ */
@Deprecated
public void setJsonParserConfiguration(JSONParserConfiguration jsonParserConfiguration) { public void setJsonParserConfiguration(JSONParserConfiguration jsonParserConfiguration) {
this.jsonParserConfiguration = jsonParserConfiguration; this.jsonParserConfiguration = jsonParserConfiguration;
} }
@ -511,21 +479,11 @@ public class JSONTokener {
throw this.syntaxError("Missing value"); throw this.syntaxError("Missing value");
} }
Object obj = JSONObject.stringToValue(string); Object obj = JSONObject.stringToValue(string);
// if obj is a boolean, look at string // Strict mode only allows strings with explicit double quotes
if (jsonParserConfiguration != null && if (jsonParserConfiguration != null &&
jsonParserConfiguration.isStrictMode()) { jsonParserConfiguration.isStrictMode() &&
if (obj instanceof Boolean && !"true".equals(string) && !"false".equals(string)) { obj instanceof String) {
// Strict mode only allows lowercase true or false throw this.syntaxError(String.format("Strict mode error: Value '%s' is not surrounded by quotes", obj));
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not lowercase boolean", obj));
}
else if (obj == JSONObject.NULL && !"null".equals(string)) {
// Strint mode only allows lowercase null
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not lowercase null", obj));
}
else if (obj instanceof String) {
// Strict mode only allows strings with explicit double quotes
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not surrounded by quotes", obj));
}
} }
return obj; return obj;
} }

View File

@ -355,20 +355,10 @@ public class XML {
&& TYPE_ATTR.equals(string)) { && TYPE_ATTR.equals(string)) {
xmlXsiTypeConverter = config.getXsiTypeMap().get(token); xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
} else if (!nilAttributeFound) { } else if (!nilAttributeFound) {
Object obj = stringToValue((String) token); jsonObject.accumulate(string,
if (obj instanceof Boolean) { config.isKeepStrings()
jsonObject.accumulate(string, ? ((String) token)
config.isKeepBooleanAsString() : stringToValue((String) token));
? ((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; token = null;
} else { } else {
@ -417,23 +407,8 @@ public class XML {
jsonObject.accumulate(config.getcDataTagName(), jsonObject.accumulate(config.getcDataTagName(),
stringToValue(string, xmlXsiTypeConverter)); stringToValue(string, xmlXsiTypeConverter));
} else { } else {
Object obj = stringToValue((String) token); jsonObject.accumulate(config.getcDataTagName(),
if (obj instanceof Boolean) { config.isKeepStrings() ? string : stringToValue(string));
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepBooleanAsString()
? ((String) token)
: obj);
} else if (obj instanceof Number) {
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepNumberAsString()
? ((String) token)
: obj);
} else if (obj == JSONObject.NULL) {
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepStrings() ? ((String) token) : obj);
} else {
jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token));
}
} }
} }
@ -713,44 +688,6 @@ public class XML {
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL); 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 * Convert a well-formed (but not necessarily valid) XML into a
* JSONObject. Some information may be lost in this transformation because * JSONObject. Some information may be lost in this transformation because
@ -809,38 +746,6 @@ public class XML {
return toJSONObject(new StringReader(string), keepStrings); 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 * Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject. Some information may be lost in this transformation because * JSONObject. Some information may be lost in this transformation because

View File

@ -22,16 +22,6 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/ */
// public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512; // We could override // 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. */ /** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL public static final XMLParserConfiguration ORIGINAL
= new XMLParserConfiguration(); = new XMLParserConfiguration();
@ -152,9 +142,7 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/ */
@Deprecated @Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) { public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
super(false, DEFAULT_MAXIMUM_NESTING_DEPTH); super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
this.keepNumberAsString = keepStrings;
this.keepBooleanAsString = keepStrings;
this.cDataTagName = cDataTagName; this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull; this.convertNilAttributeToNull = convertNilAttributeToNull;
} }
@ -175,10 +163,8 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/ */
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList, final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList,
final int maxNestingDepth, final boolean closeEmptyTag, final boolean keepNumberAsString, final boolean keepBooleanAsString) { final int maxNestingDepth, final boolean closeEmptyTag) {
super(false, maxNestingDepth); super(keepStrings, maxNestingDepth);
this.keepNumberAsString = keepNumberAsString;
this.keepBooleanAsString = keepBooleanAsString;
this.cDataTagName = cDataTagName; this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull; this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap); this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
@ -203,9 +189,7 @@ public class XMLParserConfiguration extends ParserConfiguration {
this.xsiTypeMap, this.xsiTypeMap,
this.forceList, this.forceList,
this.maxNestingDepth, this.maxNestingDepth,
this.closeEmptyTag, this.closeEmptyTag
this.keepNumberAsString,
this.keepBooleanAsString
); );
config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace; config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
return config; return config;
@ -223,43 +207,7 @@ public class XMLParserConfiguration extends ParserConfiguration {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public XMLParserConfiguration withKeepStrings(final boolean newVal) { public XMLParserConfiguration withKeepStrings(final boolean newVal) {
XMLParserConfiguration newConfig = this.clone(); return super.withKeepStrings(newVal);
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;
} }
/** /**
@ -273,26 +221,6 @@ public class XMLParserConfiguration extends ParserConfiguration {
return this.cDataTagName; 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 * 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 * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA

View File

@ -168,33 +168,6 @@ 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. * Assert that there is no error for a single escaped quote within a properly embedded quote.
*/ */

View File

@ -1,107 +0,0 @@
package org.json.junit;
import org.json.HTTPTokener;
import org.json.JSONException;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests for JSON-Java HTTPTokener.java
*/
public class HTTPTokenerTest {
/**
* Test parsing a simple unquoted token.
*/
@Test
public void parseSimpleToken() {
HTTPTokener tokener = new HTTPTokener("Content-Type");
String token = tokener.nextToken();
assertEquals("Content-Type", token);
}
/**
* Test parsing multiple tokens separated by whitespace.
*/
@Test
public void parseMultipleTokens() {
HTTPTokener tokener = new HTTPTokener("Content-Type application/json");
String token1 = tokener.nextToken();
String token2 = tokener.nextToken();
assertEquals("Content-Type", token1);
assertEquals("application/json", token2);
}
/**
* Test parsing a double-quoted token.
*/
@Test
public void parseDoubleQuotedToken() {
HTTPTokener tokener = new HTTPTokener("\"application/json\"");
String token = tokener.nextToken();
assertEquals("application/json", token);
}
/**
* Test parsing a single-quoted token.
*/
@Test
public void parseSingleQuotedToken() {
HTTPTokener tokener = new HTTPTokener("'application/json'");
String token = tokener.nextToken();
assertEquals("application/json", token);
}
/**
* Test parsing a quoted token that includes spaces and semicolons.
*/
@Test
public void parseQuotedTokenWithSpaces() {
HTTPTokener tokener = new HTTPTokener("\"text/html; charset=UTF-8\"");
String token = tokener.nextToken();
assertEquals("text/html; charset=UTF-8", token);
}
/**
* Test that unterminated quoted strings throw a JSONException.
*/
@Test
public void throwExceptionOnUnterminatedString() {
HTTPTokener tokener = new HTTPTokener("\"incomplete");
JSONException exception = assertThrows(JSONException.class, tokener::nextToken);
assertTrue(exception.getMessage().contains("Unterminated string"));
}
/**
* Test behavior with empty input string.
*/
@Test
public void parseEmptyInput() {
HTTPTokener tokener = new HTTPTokener("");
String token = tokener.nextToken();
assertEquals("", token);
}
/**
* Test behavior with input consisting only of whitespace.
*/
@Test
public void parseWhitespaceOnly() {
HTTPTokener tokener = new HTTPTokener(" \t \n ");
String token = tokener.nextToken();
assertEquals("", token);
}
/**
* Test parsing tokens separated by multiple whitespace characters.
*/
@Test
public void parseTokensWithMultipleWhitespace() {
HTTPTokener tokener = new HTTPTokener("GET /index.html");
String method = tokener.nextToken();
String path = tokener.nextToken();
assertEquals("GET", method);
assertEquals("/index.html", path);
}
}

View File

@ -8,7 +8,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -229,19 +228,6 @@ public class JSONArrayTest {
Util.checkJSONArrayMaps(jaInt); 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. * Tests consecutive calls to putAll with array and collection.
*/ */
@ -490,18 +476,13 @@ public class JSONArrayTest {
*/ */
@Test @Test
public void unquotedText() { 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(); JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
try { System.out.println("Skipping JSONArrayTest unquotedText() when strictMode default is true");
JSONArray jsonArray = new JSONArray(str);
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else { } else {
JSONArray jsonArray = new JSONArray(str); String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
JSONArray jsonArray = new JSONArray(str);
List<Object> expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
assertEquals(expected, jsonArray.toList()); assertEquals(expected, jsonArray.toList());
} }
} }
@ -1528,14 +1509,6 @@ public class JSONArrayTest {
new JSONArray(array); 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) { public static ArrayList<Object> buildNestedArray(int maxDepth) {
if (maxDepth <= 0) { if (maxDepth <= 0) {
return new ArrayList<>(); return new ArrayList<>();

View File

@ -218,16 +218,11 @@ public class JSONObjectTest {
*/ */
@Test @Test
public void unquotedText() { 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(); JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
try { System.out.println("Skipping JSONObjectTest unquotedText() when strictMode default is true");
JSONObject jsonObject = new JSONObject(str);
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else { } else {
String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
JSONObject jsonObject = new JSONObject(str); JSONObject jsonObject = new JSONObject(str);
String textStr = jsonObject.toString(); String textStr = jsonObject.toString();
assertTrue("expected key1", textStr.contains("\"key1\"")); assertTrue("expected key1", textStr.contains("\"key1\""));
@ -617,46 +612,6 @@ public class JSONObjectTest {
Util.checkJSONObjectMaps(jsonObject); 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 * JSONObject built from a bean. In this case all but one of the
* bean getters return valid JSON types * bean getters return valid JSON types
@ -1119,29 +1074,24 @@ public class JSONObjectTest {
*/ */
@Test @Test
public void jsonInvalidNumberValues() { 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(); JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
try { System.out.println("Skipping JSONObjectTest jsonInvalidNumberValues() when strictMode default is true");
JSONObject jsonObject = new JSONObject(str);
assertEquals("Expected to throw exception due to invalid string", true, false);
} catch (JSONException e) { }
} else { } 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); JSONObject jsonObject = new JSONObject(str);
Object obj; Object obj;
obj = jsonObject.get("hexNumber"); obj = jsonObject.get("hexNumber");
@ -2316,417 +2266,332 @@ public class JSONObjectTest {
} }
} }
/**
* Explore how JSONObject handles parsing errors.
*/
@SuppressWarnings({"boxing", "unused"})
@Test @Test
public void parsingErrorTrailingCurlyBrace () { public void jsonObjectParsingErrors() {
try { JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
// does not end with '}' if (jsonParserConfiguration.isStrictMode()) {
String str = "{"; System.out.println("Skipping JSONObjectTest jaonObjectParsingErrors() when strictMode default is true");
assertNull("Expected an exception", new JSONObject(str)); } else {
} catch (JSONException e) { try {
assertEquals("Expecting an exception message", // does not start with '{'
"A JSONObject text must end with '}' at 1 [character 2 line 1]", String str = "abc";
e.getMessage()); 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());
}
@Test try {
public void parsingErrorInitialCurlyBrace() { // multiple putOnce key
try { JSONObject jsonObject = new JSONObject("{}");
// does not start with '{' jsonObject.putOnce("hello", "world");
String str = "abc"; jsonObject.putOnce("hello", "world!");
assertNull("Expected an exception", new JSONObject(str)); fail("Expected an exception");
} catch (JSONException e) { } catch (JSONException e) {
assertEquals("Expecting an exception message", assertTrue("", true);
"A JSONObject text must begin with '{' at 1 [character 2 line 1]", }
e.getMessage()); try {
} // test validity of invalid double
} JSONObject.testValidity(Double.NaN);
fail("Expected an exception");
@Test } catch (JSONException e) {
public void parsingErrorNoColon() { assertTrue("", true);
try { }
// key with no ':' try {
String str = "{\"myKey\" = true}"; // test validity of invalid float
assertNull("Expected an exception", new JSONObject(str)); JSONObject.testValidity(Float.NEGATIVE_INFINITY);
} catch (JSONException e) { fail("Expected an exception");
assertEquals("Expecting an exception message", } catch (JSONException e) {
"Expected a ':' after a key at 10 [character 11 line 1]", assertTrue("", true);
e.getMessage()); }
} try {
} // test exception message when including a duplicate key (level 0)
String str = "{\n"
@Test + " \"attr01\":\"value-01\",\n"
public void parsingErrorNoCommaSeparator() { + " \"attr02\":\"value-02\",\n"
try { + " \"attr03\":\"value-03\",\n"
// entries with no ',' separator + " \"attr03\":\"value-04\"\n"
String str = "{\"myKey\":true \"myOtherKey\":false}"; + "}";
assertNull("Expected an exception", new JSONObject(str)); new JSONObject(str);
} catch (JSONException e) { fail("Expected an exception");
assertEquals("Expecting an exception message", } catch (JSONException e) {
"Expected a ',' or '}' at 15 [character 16 line 1]", assertEquals("Expecting an expection message",
e.getMessage()); "Duplicate key \"attr03\" at 90 [character 13 line 5]",
} e.getMessage());
} }
try {
@Test // test exception message when including a duplicate key (level 0) holding an object
public void parsingErrorKeyIsNestedMap() { String str = "{\n"
try { + " \"attr01\":\"value-01\",\n"
// key is a nested map + " \"attr02\":\"value-02\",\n"
String str = "{{\"foo\": \"bar\"}: \"baz\"}"; + " \"attr03\":\"value-03\",\n"
assertNull("Expected an exception", new JSONObject(str)); + " \"attr03\": {"
} catch (JSONException e) { + " \"attr04-01\":\"value-04-01\",n"
assertEquals("Expecting an exception message", + " \"attr04-02\":\"value-04-02\",n"
"Missing value at 1 [character 2 line 1]", + " \"attr04-03\":\"value-04-03\"n"
e.getMessage()); + " }\n"
} + "}";
} new JSONObject(str);
fail("Expected an exception");
@Test } catch (JSONException e) {
public void parsingErrorKeyIsNestedArrayWithMap() { assertEquals("Expecting an expection message",
try { "Duplicate key \"attr03\" at 90 [character 13 line 5]",
// key is a nested array containing a map e.getMessage());
String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}"; }
assertNull("Expected an exception", new JSONObject(str)); try {
} catch (JSONException e) { // test exception message when including a duplicate key (level 0) holding an array
assertEquals("Expecting an exception message", String str = "{\n"
"Missing value at 9 [character 10 line 1]", + " \"attr01\":\"value-01\",\n"
e.getMessage()); + " \"attr02\":\"value-02\",\n"
} + " \"attr03\":\"value-03\",\n"
} + " \"attr03\": [\n"
+ " {"
@Test + " \"attr04-01\":\"value-04-01\",n"
public void parsingErrorKeyContainsCurlyBrace() { + " \"attr04-02\":\"value-04-02\",n"
try { + " \"attr04-03\":\"value-04-03\"n"
// key contains } + " }\n"
String str = "{foo}: 2}"; + " ]\n"
assertNull("Expected an exception", new JSONObject(str)); + "}";
} catch (JSONException e) { new JSONObject(str);
// assertEquals("Expecting an exception message", fail("Expected an exception");
// "Expected a ':' after a key at 5 [character 6 line 1]", } catch (JSONException e) {
// e.getMessage()); assertEquals("Expecting an expection message",
} "Duplicate key \"attr03\" at 90 [character 13 line 5]",
} e.getMessage());
}
@Test try {
public void parsingErrorKeyContainsSquareBrace() { // test exception message when including a duplicate key (level 1)
try { String str = "{\n"
// key contains ] + " \"attr01\":\"value-01\",\n"
String str = "{foo]: 2}"; + " \"attr02\":\"value-02\",\n"
assertNull("Expected an exception", new JSONObject(str)); + " \"attr03\":\"value-03\",\n"
} catch (JSONException e) { + " \"attr04\": {\n"
// assertEquals("Expecting an exception message", + " \"attr04-01\":\"value04-01\",\n"
// "Expected a ':' after a key at 5 [character 6 line 1]", + " \"attr04-02\":\"value04-02\",\n"
// e.getMessage()); + " \"attr04-03\":\"value04-03\",\n"
} + " \"attr04-03\":\"value04-04\"\n"
} + " }\n"
+ "}";
@Test new JSONObject(str);
public void parsingErrorKeyContainsBinaryZero() { fail("Expected an exception");
try { } catch (JSONException e) {
// \0 after , assertEquals("Expecting an expection message",
String str = "{\"myKey\":true, \0\"myOtherKey\":false}"; "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
assertNull("Expected an exception", new JSONObject(str)); e.getMessage());
} catch (JSONException e) { }
assertEquals("Expecting an exception message", try {
"A JSONObject text must end with '}' at 15 [character 16 line 1]", // test exception message when including a duplicate key (level 1) holding an object
e.getMessage()); String str = "{\n"
} + " \"attr01\":\"value-01\",\n"
} + " \"attr02\":\"value-02\",\n"
+ " \"attr03\":\"value-03\",\n"
@Test + " \"attr04\": {\n"
public void parsingErrorAppendToWrongValue() { + " \"attr04-01\":\"value04-01\",\n"
try { + " \"attr04-02\":\"value04-02\",\n"
// append to wrong value + " \"attr04-03\":\"value04-03\",\n"
String str = "{\"myKey\":true, \"myOtherKey\":false}"; + " \"attr04-03\": {\n"
JSONObject jsonObject = new JSONObject(str); + " \"attr04-04-01\":\"value04-04-01\",\n"
jsonObject.append("myKey", "hello"); + " \"attr04-04-02\":\"value04-04-02\",\n"
fail("Expected an exception"); + " \"attr04-04-03\":\"value04-04-03\",\n"
} catch (JSONException e) { + " }\n"
assertEquals("Expecting an exception message", + " }\n"
"JSONObject[\"myKey\"] is not a JSONArray (null).", + "}";
e.getMessage()); new JSONObject(str);
} fail("Expected an exception");
} } catch (JSONException e) {
assertEquals("Expecting an expection message",
@Test "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
public void parsingErrorIncrementWrongValue() { e.getMessage());
try { }
// increment wrong value try {
String str = "{\"myKey\":true, \"myOtherKey\":false}"; // test exception message when including a duplicate key (level 1) holding an array
JSONObject jsonObject = new JSONObject(str); String str = "{\n"
jsonObject.increment("myKey"); + " \"attr01\":\"value-01\",\n"
fail("Expected an exception"); + " \"attr02\":\"value-02\",\n"
} catch (JSONException e) { + " \"attr03\":\"value-03\",\n"
assertEquals("Expecting an exception message", + " \"attr04\": {\n"
"Unable to increment [\"myKey\"].", + " \"attr04-01\":\"value04-01\",\n"
e.getMessage()); + " \"attr04-02\":\"value04-02\",\n"
} + " \"attr04-03\":\"value04-03\",\n"
} + " \"attr04-03\": [\n"
@Test + " {\n"
public void parsingErrorInvalidKey() { + " \"attr04-04-01\":\"value04-04-01\",\n"
try { + " \"attr04-04-02\":\"value04-04-02\",\n"
// invalid key + " \"attr04-04-03\":\"value04-04-03\",\n"
String str = "{\"myKey\":true, \"myOtherKey\":false}"; + " }\n"
JSONObject jsonObject = new JSONObject(str); + " ]\n"
jsonObject.get(null); + " }\n"
fail("Expected an exception"); + "}";
} catch (JSONException e) { new JSONObject(str);
assertEquals("Expecting an exception message", fail("Expected an exception");
"Null key.", } catch (JSONException e) {
e.getMessage()); assertEquals("Expecting an expection message",
} "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
} e.getMessage());
}
@Test try {
public void parsingErrorNumberToString() { // test exception message when including a duplicate key in object (level 0) within an array
try { String str = "[\n"
// invalid numberToString() + " {\n"
JSONObject.numberToString((Number) null); + " \"attr01\":\"value-01\",\n"
fail("Expected an exception"); + " \"attr02\":\"value-02\"\n"
} catch (JSONException e) { + " },\n"
assertEquals("Expecting an exception message", + " {\n"
"Null pointer", + " \"attr01\":\"value-01\",\n"
e.getMessage()); + " \"attr01\":\"value-02\"\n"
} + " }\n"
} + "]";
new JSONArray(str);
@Test fail("Expected an exception");
public void parsingErrorPutOnceDuplicateKey() { } catch (JSONException e) {
try { assertEquals("Expecting an expection message",
// multiple putOnce key "Duplicate key \"attr01\" at 124 [character 17 line 8]",
JSONObject jsonObject = new JSONObject("{}"); e.getMessage());
jsonObject.putOnce("hello", "world"); }
jsonObject.putOnce("hello", "world!"); try {
fail("Expected an exception"); // test exception message when including a duplicate key in object (level 1) within an array
} catch (JSONException e) { String str = "[\n"
assertTrue("", true); + " {\n"
} + " \"attr01\":\"value-01\",\n"
} + " \"attr02\": {\n"
+ " \"attr02-01\":\"value-02-01\",\n"
@Test + " \"attr02-02\":\"value-02-02\"\n"
public void parsingErrorInvalidDouble() { + " }\n"
try { + " },\n"
// test validity of invalid double + " {\n"
JSONObject.testValidity(Double.NaN); + " \"attr01\":\"value-01\",\n"
fail("Expected an exception"); + " \"attr02\": {\n"
} catch (JSONException e) { + " \"attr02-01\":\"value-02-01\",\n"
assertTrue("", true); + " \"attr02-01\":\"value-02-02\"\n"
} + " }\n"
} + " }\n"
+ "]";
@Test new JSONArray(str);
public void parsingErrorInvalidFloat() { fail("Expected an exception");
try { } catch (JSONException e) {
// test validity of invalid float assertEquals("Expecting an expection message",
JSONObject.testValidity(Float.NEGATIVE_INFINITY); "Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
fail("Expected an exception"); e.getMessage());
} 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());
} }
} }
@ -3988,65 +3853,6 @@ public class JSONObjectTest {
assertEquals(j3.getString("hex6"), "0011"); 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); });
}
@Test
public void test_strictModeWithMisCasedBooleanOrNullValue(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
try{
new JSONObject("{\"a\":True}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{\"a\":TRUE}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{\"a\":nUlL}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
}
@Test
public void test_strictModeWithInappropriateKey(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
// Parsing the following objects should fail
try{
new JSONObject("{true : 3}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{TRUE : 3}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{1 : 3}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
}
/** /**
* Method to build nested map of max maxDepth * Method to build nested map of max maxDepth
* *
@ -4062,36 +3868,4 @@ public class JSONObjectTest {
return nestedMap; return nestedMap;
} }
/**
* Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
* using a custom {@link JSONParserConfiguration} that enables the use of native nulls.
*
* <p>This test ensures that uninitialized fields in the bean are serialized correctly
* into the resulting JSON object, and their keys are present in the JSON string output.</p>
*/
@Test
public void jsonObjectParseNullFieldsWithParserConfiguration() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
RecursiveBean bean = new RecursiveBean(null);
JSONObject jsonObject = new JSONObject(bean, jsonParserConfiguration.withUseNativeNulls(true));
assertTrue("name key should be present", jsonObject.has("name"));
assertTrue("ref key should be present", jsonObject.has("ref"));
assertTrue("ref2 key should be present", jsonObject.has("ref2"));
}
/**
* Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
* without using a custom {@link JSONParserConfiguration}.
*
* <p>This test ensures that uninitialized fields in the bean are not serialized
* into the resulting JSON object, and the object remains empty.</p>
*/
@Test
public void jsonObjectParseNullFieldsWithoutParserConfiguration() {
RecursiveBean bean = new RecursiveBean(null);
JSONObject jsonObject = new JSONObject(bean);
assertTrue("JSONObject should be empty", jsonObject.isEmpty());
}
} }

View File

@ -4,7 +4,6 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONParserConfiguration; import org.json.JSONParserConfiguration;
import org.json.JSONTokener;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;
@ -15,10 +14,7 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class JSONParserConfigurationTest { public class JSONParserConfigurationTest {
private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}"; private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
@ -36,32 +32,6 @@ public class JSONParserConfigurationTest {
assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key")); 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 @Test
public void verifyDuplicateKeyThenMaxDepth() { public void verifyDuplicateKeyThenMaxDepth() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
@ -520,40 +490,6 @@ public class JSONParserConfigurationTest {
je.getMessage()); 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 method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
* this class. * this class.

View File

@ -16,7 +16,10 @@ import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import org.json.*; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.junit.Test; import org.junit.Test;
/** /**
@ -95,17 +98,7 @@ public class JSONTokenerTest {
checkValid(" [] ",JSONArray.class); checkValid(" [] ",JSONArray.class);
checkValid("[1,2]",JSONArray.class); checkValid("[1,2]",JSONArray.class);
checkValid("\n\n[1,2]\n\n",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 @Test
@ -332,42 +325,4 @@ public class JSONTokenerTest {
assertEquals("Stream closed", exception.getMessage()); 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,18 +574,15 @@ public class XMLConfigurationTest {
XMLParserConfiguration keepStringsAndCloseEmptyTag = keepStrings.withCloseEmptyTag(true); XMLParserConfiguration keepStringsAndCloseEmptyTag = keepStrings.withCloseEmptyTag(true);
XMLParserConfiguration keepDigits = keepStringsAndCloseEmptyTag.withKeepStrings(false); XMLParserConfiguration keepDigits = keepStringsAndCloseEmptyTag.withKeepStrings(false);
XMLParserConfiguration keepDigitsAndNoCloseEmptyTag = keepDigits.withCloseEmptyTag(false); XMLParserConfiguration keepDigitsAndNoCloseEmptyTag = keepDigits.withCloseEmptyTag(false);
assertTrue(keepStrings.isKeepNumberAsString()); assertTrue(keepStrings.isKeepStrings());
assertTrue(keepStrings.isKeepBooleanAsString());
assertFalse(keepStrings.isCloseEmptyTag()); assertFalse(keepStrings.isCloseEmptyTag());
assertTrue(keepStringsAndCloseEmptyTag.isKeepNumberAsString()); assertTrue(keepStringsAndCloseEmptyTag.isKeepStrings());
assertTrue(keepStringsAndCloseEmptyTag.isKeepBooleanAsString());
assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag()); assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag());
assertFalse(keepDigits.isKeepNumberAsString()); assertFalse(keepDigits.isKeepStrings());
assertFalse(keepDigits.isKeepBooleanAsString());
assertTrue(keepDigits.isCloseEmptyTag()); assertTrue(keepDigits.isCloseEmptyTag());
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepNumberAsString()); assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepStrings());
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepBooleanAsString());
assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag()); assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag());
} }
/** /**
@ -770,67 +767,6 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected); 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><id>null</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\",null],\"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><id>null</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0,null],\"title\":\"True\"}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepBooleanAsString(true));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
/**
* null is "null" when keepStrings == true
*/
@Test
public void testToJSONArray_jsonOutput_null_withKeepString() {
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>null</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"null\"}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepStrings(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. * JSON string cannot be reverted to original xml.
*/ */

View File

@ -1,81 +0,0 @@
package org.json.junit;
import org.json.XMLTokener;
import org.junit.Test;
import java.io.StringReader;
import static org.junit.Assert.*;
/**
* Tests for JSON-Java XMLTokener.java
*/
public class XMLTokenerTest {
/**
* Tests that nextCDATA() correctly extracts content from within a CDATA section.
*/
@Test
public void testNextCDATA() {
String xml = "This is <![CDATA[ some <CDATA> content ]]> after";
XMLTokener tokener = new XMLTokener(new StringReader(xml));
tokener.skipPast("<![CDATA[");
String cdata = tokener.nextCDATA();
assertEquals(" some <CDATA> content ", cdata);
}
/**
* Tests that nextContent() returns plain text content before a tag.
*/
@Test
public void testNextContentWithText() {
String xml = "Some content<nextTag>";
XMLTokener tokener = new XMLTokener(xml);
Object content = tokener.nextContent();
assertEquals("Some content", content);
}
/**
* Tests that nextContent() returns '<' character when starting with a tag.
*/
@Test
public void testNextContentWithTag() {
String xml = "<tag>";
XMLTokener tokener = new XMLTokener(xml);
Object content = tokener.nextContent();
assertEquals('<', content);
}
/**
* Tests that nextEntity() resolves a known entity like &amp; correctly.
*/
@Test
public void testNextEntityKnown() {
XMLTokener tokener = new XMLTokener("amp;");
Object result = tokener.nextEntity('&');
assertEquals("&", result);
}
/**
* Tests that nextEntity() preserves unknown entities by returning them unchanged.
*/
@Test
public void testNextEntityUnknown() {
XMLTokener tokener = new XMLTokener("unknown;");
tokener.next(); // skip 'u'
Object result = tokener.nextEntity('&');
assertEquals("&nknown;", result); // malformed start to simulate unknown
}
/**
* Tests skipPast() to ensure the cursor moves past the specified string.
*/
@Test
public void testSkipPast() {
String xml = "Ignore this... endHere more text";
XMLTokener tokener = new XMLTokener(xml);
tokener.skipPast("endHere");
assertEquals(' ', tokener.next()); // should be the space after "endHere"
}
}