mirror of
https://github.com/stleary/JSON-java.git
synced 2025-08-02 11:05:28 -04:00
Compare commits
90 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
78137d389d | ||
|
38c3a0bb3f | ||
|
ebd9a17a3b | ||
|
82432f0245 | ||
|
e762629bcc | ||
|
7fc41a6c0e | ||
|
d5d82cdb87 | ||
|
0a9364e920 | ||
|
c91b728386 | ||
|
fdaeb486ed | ||
|
f0a78aff61 | ||
|
a79e8a15e5 | ||
|
7bb3df8ebf | ||
|
3dce55794f | ||
|
d7593fb808 | ||
|
1eed44a59e | ||
|
7eccadefcd | ||
|
7b0d1942b4 | ||
|
a729c2077a | ||
|
7ac773be72 | ||
|
7da120e631 | ||
|
197afddbfb | ||
|
1bdaacc8b0 | ||
|
c882783d58 | ||
|
5063d314a5 | ||
|
916fba5d39 | ||
|
aac376f305 | ||
|
32e56da786 | ||
|
50330430ce | ||
|
f1935f5254 | ||
|
e800cc349f | ||
|
72a1a48173 | ||
|
a381060f81 | ||
|
dadc3e59dc | ||
|
24fafcffeb | ||
|
418d5e9973 | ||
|
82a02d879e | ||
|
2184ef34d1 | ||
|
8e65eaa992 | ||
|
74439cf696 | ||
|
53da5ce2a9 | ||
|
2e9ad6ff5a | ||
|
8dbf03e76b | ||
|
4917e3579d | ||
|
45ec164faa | ||
|
d4c5136c21 | ||
|
fd0cca3586 | ||
|
50a5ce256b | ||
|
1afd7cd6bc | ||
|
7751b397bf | ||
|
5d1c789490 | ||
|
d1327c2da3 | ||
|
b2943b8fd0 | ||
|
628d8c42d9 | ||
|
76ee4312b3 | ||
|
4a662316f7 | ||
|
6452a6f38d | ||
|
ae4f4afcc7 | ||
|
8a86894c63 | ||
|
f30167e7c0 | ||
|
75e5a3d646 | ||
|
3919abd69a | ||
|
f112a091aa | ||
|
42afb34045 | ||
|
a746322e57 | ||
|
c524cd17a0 | ||
|
52f249c71e | ||
|
1689fc28cf | ||
|
22f8290840 | ||
|
8b857da467 | ||
|
07b1291448 | ||
|
1d81e8879a | ||
|
4c873a1db4 | ||
|
6631b80e8f | ||
|
9218f28db8 | ||
|
94341cd663 | ||
|
afd9a6fbb7 | ||
|
54470e6b56 | ||
|
dde9d7eceb | ||
|
8c427d9101 | ||
|
4bbbe77446 | ||
|
ad44a9274c | ||
|
3b7ba07531 | ||
|
215f4268bf | ||
|
ca1c6830c9 | ||
|
2e153737b1 | ||
|
391c86931b | ||
|
ed8c73964a | ||
|
324090a876 | ||
|
41c6e9e81e |
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# 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"
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
26
.github/workflows/pipeline.yml
vendored
26
.github/workflows/pipeline.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
jar cvf target/org.json.jar -C target/classes .
|
||||
- name: Upload JAR 1.6
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Create java 1.6 JAR
|
||||
path: target/*.jar
|
||||
@ -64,13 +64,13 @@ jobs:
|
||||
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
|
||||
- name: Upload Test Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Results ${{ matrix.java }}
|
||||
path: target/surefire-reports/
|
||||
- name: Upload Test Report ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Report ${{ matrix.java }}
|
||||
path: target/site/
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
|
||||
- name: Upload Package Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Package Jar ${{ matrix.java }}
|
||||
path: target/*.jar
|
||||
@ -112,13 +112,13 @@ jobs:
|
||||
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
|
||||
- name: Upload Test Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Results ${{ matrix.java }}
|
||||
path: target/surefire-reports/
|
||||
- name: Upload Test Report ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Report ${{ matrix.java }}
|
||||
path: target/site/
|
||||
@ -126,7 +126,7 @@ jobs:
|
||||
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
|
||||
- name: Upload Package Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Package Jar ${{ matrix.java }}
|
||||
path: target/*.jar
|
||||
@ -160,13 +160,13 @@ jobs:
|
||||
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
|
||||
- name: Upload Test Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Results ${{ matrix.java }}
|
||||
path: target/surefire-reports/
|
||||
- name: Upload Test Report ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Report ${{ matrix.java }}
|
||||
path: target/site/
|
||||
@ -174,7 +174,7 @@ jobs:
|
||||
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
|
||||
- name: Upload Package Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Package Jar ${{ matrix.java }}
|
||||
path: target/*.jar
|
||||
@ -208,13 +208,13 @@ jobs:
|
||||
mvn site -D generateReports=false -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }}
|
||||
- name: Upload Test Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Results ${{ matrix.java }}
|
||||
path: target/surefire-reports/
|
||||
- name: Upload Test Report ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Test Report ${{ matrix.java }}
|
||||
path: target/site/
|
||||
@ -222,7 +222,7 @@ jobs:
|
||||
run: mvn clean package -D maven.compiler.source=${{ matrix.java }} -D maven.compiler.target=${{ matrix.java }} -D maven.test.skip=true -D maven.site.skip=true
|
||||
- name: Upload Package Results ${{ matrix.java }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Package Jar ${{ matrix.java }}
|
||||
path: target/*.jar
|
||||
|
14
README.md
14
README.md
@ -10,7 +10,7 @@ JSON in Java [package org.json]
|
||||
[](https://github.com/stleary/JSON-java/actions/workflows/pipeline.yml)
|
||||
[](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/20241224/json-20241224.jar)**
|
||||
**[Click here if you just want the latest release jar file.](https://search.maven.org/remotecontent?filepath=org/json/json/20250517/json-20250517.jar)**
|
||||
|
||||
|
||||
# Overview
|
||||
@ -97,6 +97,18 @@ Execute the test suite with Gradlew:
|
||||
gradlew clean build test
|
||||
```
|
||||
|
||||
*Optional* Execute the test suite in strict mode with Gradlew:
|
||||
|
||||
```shell
|
||||
gradlew testWithStrictMode
|
||||
```
|
||||
|
||||
*Optional* Execute the test suite in strict mode with Maven:
|
||||
|
||||
```shell
|
||||
mvn test -P test-strict-mode
|
||||
```
|
||||
|
||||
# Notes
|
||||
|
||||
For more information, please see [NOTES.md](https://github.com/stleary/JSON-java/blob/master/docs/NOTES.md)
|
||||
|
88
build.gradle
88
build.gradle
@ -3,9 +3,10 @@
|
||||
*/
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
// apply plugin: 'jacoco'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
// for now, publishing to maven is still a manual process
|
||||
//plugins {
|
||||
// id 'java'
|
||||
//id 'maven-publish'
|
||||
@ -19,6 +20,17 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
// To view the report open build/reports/jacoco/test/html/index.html
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
html.required = true
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
finalizedBy jacocoTestReport
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'com.jayway.jsonpath:json-path:2.9.0'
|
||||
@ -30,7 +42,7 @@ subprojects {
|
||||
}
|
||||
|
||||
group = 'org.json'
|
||||
version = 'v20230618-SNAPSHOT'
|
||||
version = 'v20250517-SNAPSHOT'
|
||||
description = 'JSON in Java'
|
||||
sourceCompatibility = '1.8'
|
||||
|
||||
@ -53,3 +65,75 @@ publishing {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
// Add these imports at the top of your build.gradle file
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
// Your existing build configurations...
|
||||
|
||||
// Add a new task to modify the file
|
||||
task modifyStrictMode {
|
||||
doLast {
|
||||
println "Modifying JSONParserConfiguration.java to enable strictMode..."
|
||||
|
||||
def filePath = project.file('src/main/java/org/json/JSONParserConfiguration.java')
|
||||
|
||||
if (!filePath.exists()) {
|
||||
throw new GradleException("Could not find file: ${filePath.absolutePath}")
|
||||
}
|
||||
|
||||
// Create a backup of the original file
|
||||
def backupFile = new File(filePath.absolutePath + '.bak')
|
||||
Files.copy(filePath.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
|
||||
// Read and modify the file content
|
||||
def content = filePath.text
|
||||
def modifiedContent = content.replace('// this.strictMode = true;', 'this.strictMode = true;')
|
||||
|
||||
// Write the modified content back to the file
|
||||
filePath.text = modifiedContent
|
||||
|
||||
println "File modified successfully at: ${filePath.absolutePath}"
|
||||
}
|
||||
}
|
||||
|
||||
// Add a task to restore the original file
|
||||
task restoreStrictMode {
|
||||
doLast {
|
||||
println "Restoring original JSONParserConfiguration.java..."
|
||||
|
||||
def filePath = project.file('src/main/java/org/json/JSONParserConfiguration.java')
|
||||
def backupFile = new File(filePath.absolutePath + '.bak')
|
||||
|
||||
if (backupFile.exists()) {
|
||||
Files.copy(backupFile.toPath(), filePath.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
backupFile.delete()
|
||||
println "Original file restored successfully at: ${filePath.absolutePath}"
|
||||
} else {
|
||||
println "Backup file not found at: ${backupFile.absolutePath}. No restoration performed."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a task to run the workflow
|
||||
task testWithStrictMode {
|
||||
dependsOn modifyStrictMode
|
||||
finalizedBy restoreStrictMode
|
||||
|
||||
doLast {
|
||||
// This will trigger a clean build and run tests with strictMode enabled
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||
exec {
|
||||
executable 'cmd'
|
||||
args '/c', 'gradlew.bat', 'clean', 'build'
|
||||
}
|
||||
} else {
|
||||
exec {
|
||||
executable './gradlew'
|
||||
args 'clean', 'build'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,12 @@ 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)
|
||||
|
||||
~~~
|
||||
20241224....Strict mode opt-in feature, and recent commits.
|
||||
20250517 Strict mode hardening 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.
|
||||
|
||||
|
53
pom.xml
53
pom.xml
@ -3,7 +3,7 @@
|
||||
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20241224</version>
|
||||
<version>20250517</version>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<name>JSON in Java</name>
|
||||
@ -200,4 +200,55 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>test-strict-mode</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>replacer</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<executions>
|
||||
<!-- Enable strict mode -->
|
||||
<execution>
|
||||
<id>enable-strict-mode</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<file>src/main/java/org/json/JSONParserConfiguration.java</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>// this.strictMode = true;</token>
|
||||
<value>this.strictMode = true;</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!-- Restore original code after tests -->
|
||||
<execution>
|
||||
<id>restore-original</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<file>src/main/java/org/json/JSONParserConfiguration.java</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>this.strictMode = true;</token>
|
||||
<value>// this.strictMode = true;</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
@ -100,11 +100,15 @@ public class CDL {
|
||||
for (;;) {
|
||||
String value = getValue(x,delimiter);
|
||||
char c = x.next();
|
||||
if (value == null ||
|
||||
(ja.length() == 0 && value.length() == 0 && c != delimiter)) {
|
||||
if (value != null) {
|
||||
ja.put(value);
|
||||
} else if (ja.length() == 0 && c != delimiter) {
|
||||
return null;
|
||||
} else {
|
||||
// This line accounts for CSV ending with no newline
|
||||
ja.put("");
|
||||
}
|
||||
ja.put(value);
|
||||
|
||||
for (;;) {
|
||||
if (c == delimiter) {
|
||||
break;
|
||||
@ -307,6 +311,17 @@ public class CDL {
|
||||
if (ja.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The following block accounts for empty datasets (no keys or vals)
|
||||
if (ja.length() == 1) {
|
||||
JSONObject j = ja.getJSONObject(0);
|
||||
if (j.length() == 1) {
|
||||
String key = j.keys().next();
|
||||
if ("".equals(key) && "".equals(j.get(key))) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
|
@ -67,12 +67,6 @@ public class JSONArray implements Iterable<Object> {
|
||||
*/
|
||||
private final ArrayList<Object> myArrayList;
|
||||
|
||||
// strict mode checks after constructor require access to this object
|
||||
private JSONTokener jsonTokener;
|
||||
|
||||
// strict mode checks after constructor require access to this object
|
||||
private JSONParserConfiguration jsonParserConfiguration;
|
||||
|
||||
/**
|
||||
* Construct an empty JSONArray.
|
||||
*/
|
||||
@ -89,7 +83,7 @@ public class JSONArray implements Iterable<Object> {
|
||||
* If there is a syntax error.
|
||||
*/
|
||||
public JSONArray(JSONTokener x) throws JSONException {
|
||||
this(x, new JSONParserConfiguration());
|
||||
this(x, x.getJsonParserConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,14 +96,7 @@ public class JSONArray implements Iterable<Object> {
|
||||
public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
|
||||
this();
|
||||
|
||||
if (this.jsonParserConfiguration == null) {
|
||||
this.jsonParserConfiguration = jsonParserConfiguration;
|
||||
}
|
||||
if (this.jsonTokener == null) {
|
||||
this.jsonTokener = x;
|
||||
this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration);
|
||||
}
|
||||
|
||||
boolean isInitial = x.getPrevious() == 0;
|
||||
if (x.nextClean() != '[') {
|
||||
throw x.syntaxError("A JSONArray text must start with '['");
|
||||
}
|
||||
@ -156,11 +143,19 @@ public class JSONArray implements Iterable<Object> {
|
||||
x.back();
|
||||
break;
|
||||
case ']':
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() &&
|
||||
x.nextClean() != 0) {
|
||||
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw x.syntaxError("Expected a ',' or ']'");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
|
||||
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,11 +171,6 @@ public class JSONArray implements Iterable<Object> {
|
||||
*/
|
||||
public JSONArray(String source) throws JSONException {
|
||||
this(source, new JSONParserConfiguration());
|
||||
// Strict mode does not allow trailing chars
|
||||
if (this.jsonParserConfiguration.isStrictMode() &&
|
||||
this.jsonTokener.nextClean() != 0) {
|
||||
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,12 +185,7 @@ public class JSONArray implements Iterable<Object> {
|
||||
* If there is a syntax error.
|
||||
*/
|
||||
public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
|
||||
this(new JSONTokener(source), jsonParserConfiguration);
|
||||
// Strict mode does not allow trailing chars
|
||||
if (this.jsonParserConfiguration.isStrictMode() &&
|
||||
this.jsonTokener.nextClean() != 0) {
|
||||
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,13 +334,11 @@ public class JSONArray implements Iterable<Object> {
|
||||
*/
|
||||
public boolean getBoolean(int index) throws JSONException {
|
||||
Object object = this.get(index);
|
||||
if (object.equals(Boolean.FALSE)
|
||||
|| (object instanceof String && ((String) object)
|
||||
.equalsIgnoreCase("false"))) {
|
||||
if (Boolean.FALSE.equals(object)
|
||||
|| (object instanceof String && "false".equalsIgnoreCase((String) object))) {
|
||||
return false;
|
||||
} else if (object.equals(Boolean.TRUE)
|
||||
|| (object instanceof String && ((String) object)
|
||||
.equalsIgnoreCase("true"))) {
|
||||
} else if (Boolean.TRUE.equals(object)
|
||||
|| (object instanceof String && "true".equalsIgnoreCase((String) object))) {
|
||||
return true;
|
||||
}
|
||||
throw wrongValueFormatException(index, "boolean", object, null);
|
||||
|
@ -111,7 +111,7 @@ public class JSONML {
|
||||
}
|
||||
} else if (c == '[') {
|
||||
token = x.nextToken();
|
||||
if (token.equals("CDATA") && x.next() == '[') {
|
||||
if ("CDATA".equals(token) && x.next() == '[') {
|
||||
if (ja != null) {
|
||||
ja.put(x.nextCDATA());
|
||||
}
|
||||
|
@ -79,17 +79,6 @@ public class JSONObject {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -152,12 +141,6 @@ public class JSONObject {
|
||||
*/
|
||||
public static final Object NULL = new Null();
|
||||
|
||||
// strict mode checks after constructor require access to this object
|
||||
private JSONTokener jsonTokener;
|
||||
|
||||
// strict mode checks after constructor require access to this object
|
||||
private JSONParserConfiguration jsonParserConfiguration;
|
||||
|
||||
/**
|
||||
* Construct an empty JSONObject.
|
||||
*/
|
||||
@ -186,7 +169,7 @@ public class JSONObject {
|
||||
for (int i = 0; i < names.length; i += 1) {
|
||||
try {
|
||||
this.putOnce(names[i], jo.opt(names[i]));
|
||||
} catch (Exception ignore) {
|
||||
} catch (Exception ignore) { // exception thrown for missing key
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,7 +184,7 @@ public class JSONObject {
|
||||
* duplicated key.
|
||||
*/
|
||||
public JSONObject(JSONTokener x) throws JSONException {
|
||||
this(x, new JSONParserConfiguration());
|
||||
this(x, x.getJsonParserConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,80 +200,134 @@ public class JSONObject {
|
||||
*/
|
||||
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
|
||||
this();
|
||||
|
||||
if (this.jsonParserConfiguration == null) {
|
||||
this.jsonParserConfiguration = jsonParserConfiguration;
|
||||
}
|
||||
if (this.jsonTokener == null) {
|
||||
this.jsonTokener = x;
|
||||
this.jsonTokener.setJsonParserConfiguration(this.jsonParserConfiguration);
|
||||
}
|
||||
|
||||
char c;
|
||||
String key;
|
||||
boolean isInitial = x.getPrevious() == 0;
|
||||
|
||||
if (x.nextClean() != '{') {
|
||||
throw x.syntaxError("A JSONObject text must begin with '{'");
|
||||
}
|
||||
for (;;) {
|
||||
c = x.nextClean();
|
||||
switch (c) {
|
||||
case 0:
|
||||
throw x.syntaxError("A JSONObject text must end with '}'");
|
||||
case '}':
|
||||
if (parseJSONObject(x, jsonParserConfiguration, isInitial)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
throw jsonTokener.syntaxError("A JSONObject text must end with '}'");
|
||||
case '}':
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
|
||||
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
key = x.nextSimpleValue(c).toString();
|
||||
obj = jsonTokener.nextSimpleValue(c);
|
||||
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 + "\"");
|
||||
}
|
||||
|
||||
// The key is followed by ':'.
|
||||
|
||||
c = x.nextClean();
|
||||
if (c != ':') {
|
||||
throw x.syntaxError("Expected a ':' after a key");
|
||||
Object value = jsonTokener.nextValue();
|
||||
// Only add value if non-null
|
||||
if (value != null) {
|
||||
this.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Use syntaxError(..) to include error location
|
||||
// Pairs are separated by ','.
|
||||
if (parseEndOfKeyValuePair(jsonTokener, jsonParserConfiguration, isInitial)) {
|
||||
doneParsing = true;
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
// Check if key exists
|
||||
boolean keyExists = this.opt(key) != null;
|
||||
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
|
||||
throw x.syntaxError("Duplicate key \"" + key + "\"");
|
||||
}
|
||||
return doneParsing;
|
||||
}
|
||||
|
||||
Object value = x.nextValue();
|
||||
// Only add value if non-null
|
||||
if (value != null) {
|
||||
this.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Pairs are separated by ','.
|
||||
|
||||
switch (x.nextClean()) {
|
||||
/**
|
||||
* Checks for valid end of key:value pair
|
||||
* @param jsonTokener Parses text as tokens
|
||||
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
|
||||
* @param isInitial True if end of JSON object, else false
|
||||
* @return
|
||||
*/
|
||||
private static boolean parseEndOfKeyValuePair(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, boolean isInitial) {
|
||||
switch (jsonTokener.nextClean()) {
|
||||
case ';':
|
||||
// In strict mode semicolon is not a valid separator
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
throw x.syntaxError("Strict mode error: Invalid character ';' found");
|
||||
throw jsonTokener.syntaxError("Strict mode error: Invalid character ';' found");
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
if (x.nextClean() == '}') {
|
||||
if (jsonTokener.nextClean() == '}') {
|
||||
// trailing commas are not allowed in strict mode
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
throw x.syntaxError("Strict mode error: Expected another object element");
|
||||
throw jsonTokener.syntaxError("Strict mode error: Expected another object element");
|
||||
}
|
||||
return;
|
||||
// End of JSON object
|
||||
return true;
|
||||
}
|
||||
if (x.end()) {
|
||||
throw x.syntaxError("A JSONObject text must end with '}'");
|
||||
if (jsonTokener.end()) {
|
||||
throw jsonTokener.syntaxError("A JSONObject text must end with '}'");
|
||||
}
|
||||
x.back();
|
||||
jsonTokener.back();
|
||||
break;
|
||||
case '}':
|
||||
return;
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
|
||||
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
// End of JSON object
|
||||
return true;
|
||||
default:
|
||||
throw x.syntaxError("Expected a ',' or '}'");
|
||||
throw jsonTokener.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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,7 +376,7 @@ public class JSONObject {
|
||||
throw new NullPointerException("Null key.");
|
||||
}
|
||||
final Object value = e.getValue();
|
||||
if (value != null) {
|
||||
if (value != null || jsonParserConfiguration.isUseNativeNulls()) {
|
||||
testValidity(value);
|
||||
this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration));
|
||||
}
|
||||
@ -408,12 +445,17 @@ public class JSONObject {
|
||||
*/
|
||||
public JSONObject(Object bean) {
|
||||
this();
|
||||
this.populateMap(bean);
|
||||
this.populateMap(bean, new JSONParserConfiguration());
|
||||
}
|
||||
|
||||
public JSONObject(Object bean, JSONParserConfiguration jsonParserConfiguration) {
|
||||
this();
|
||||
this.populateMap(bean, jsonParserConfiguration);
|
||||
}
|
||||
|
||||
private JSONObject(Object bean, Set<Object> objectsRecord) {
|
||||
this();
|
||||
this.populateMap(bean, objectsRecord);
|
||||
this.populateMap(bean, objectsRecord, new JSONParserConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,11 +498,6 @@ public class JSONObject {
|
||||
*/
|
||||
public JSONObject(String source) throws JSONException {
|
||||
this(source, new JSONParserConfiguration());
|
||||
// Strict mode does not allow trailing chars
|
||||
if (this.jsonParserConfiguration.isStrictMode() &&
|
||||
this.jsonTokener.nextClean() != 0) {
|
||||
throw new JSONException("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -478,12 +515,7 @@ public class JSONObject {
|
||||
* duplicated key.
|
||||
*/
|
||||
public JSONObject(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
|
||||
this(new JSONTokener(source), jsonParserConfiguration);
|
||||
// Strict mode does not allow trailing chars
|
||||
if (this.jsonParserConfiguration.isStrictMode() &&
|
||||
this.jsonTokener.nextClean() != 0) {
|
||||
throw new JSONException("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
this(new JSONTokener(source, jsonParserConfiguration), jsonParserConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -691,13 +723,11 @@ public class JSONObject {
|
||||
*/
|
||||
public boolean getBoolean(String key) throws JSONException {
|
||||
Object object = this.get(key);
|
||||
if (object.equals(Boolean.FALSE)
|
||||
|| (object instanceof String && ((String) object)
|
||||
.equalsIgnoreCase("false"))) {
|
||||
if (Boolean.FALSE.equals(object)
|
||||
|| (object instanceof String && "false".equalsIgnoreCase((String) object))) {
|
||||
return false;
|
||||
} else if (object.equals(Boolean.TRUE)
|
||||
|| (object instanceof String && ((String) object)
|
||||
.equalsIgnoreCase("true"))) {
|
||||
} else if (Boolean.TRUE.equals(object)
|
||||
|| (object instanceof String && "true".equalsIgnoreCase((String) object))) {
|
||||
return true;
|
||||
}
|
||||
throw wrongValueFormatException(key, "Boolean", object, null);
|
||||
@ -1280,7 +1310,7 @@ public class JSONObject {
|
||||
static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
|
||||
return objectToBigDecimal(val, defaultValue, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param val value to convert
|
||||
* @param defaultValue default value to return is the conversion doesn't work or is null.
|
||||
@ -1781,64 +1811,81 @@ public class JSONObject {
|
||||
* @throws JSONException
|
||||
* If a getter returned a non-finite number.
|
||||
*/
|
||||
private void populateMap(Object bean) {
|
||||
populateMap(bean, Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()));
|
||||
private void populateMap(Object bean, JSONParserConfiguration jsonParserConfiguration) {
|
||||
populateMap(bean, Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()), jsonParserConfiguration);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// If klass is a System class then set includeSuperClass to false.
|
||||
|
||||
boolean includeSuperClass = klass.getClassLoader() != null;
|
||||
|
||||
Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();
|
||||
Method[] methods = getMethods(klass);
|
||||
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())) {
|
||||
if (isValidMethod(method)) {
|
||||
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) {
|
||||
}
|
||||
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;
|
||||
|
||||
return includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();
|
||||
}
|
||||
|
||||
private static boolean isValidMethodName(String name) {
|
||||
return !"getClass".equals(name) && !"getDeclaringClass".equals(name);
|
||||
}
|
||||
@ -1854,7 +1901,7 @@ public class JSONObject {
|
||||
}
|
||||
}
|
||||
JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class);
|
||||
if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
|
||||
if (annotationValueNotEmpty(annotation)) {
|
||||
return annotation.value();
|
||||
}
|
||||
String key;
|
||||
@ -1880,6 +1927,46 @@ public class JSONObject {
|
||||
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
|
||||
* implementations and interfaces has the annotation.
|
||||
@ -1923,7 +2010,7 @@ public class JSONObject {
|
||||
}
|
||||
|
||||
//If the superclass is Object, no annotations will be found any more
|
||||
if (c.getSuperclass().equals(Object.class))
|
||||
if (Object.class.equals(c.getSuperclass()))
|
||||
return null;
|
||||
|
||||
try {
|
||||
@ -1981,7 +2068,7 @@ public class JSONObject {
|
||||
}
|
||||
|
||||
//If the superclass is Object, no annotations will be found any more
|
||||
if (c.getSuperclass().equals(Object.class))
|
||||
if (Object.class.equals(c.getSuperclass()))
|
||||
return -1;
|
||||
|
||||
try {
|
||||
@ -2767,13 +2854,13 @@ public class JSONObject {
|
||||
return NULL;
|
||||
}
|
||||
if (object instanceof JSONObject || object instanceof JSONArray
|
||||
|| NULL.equals(object) || object instanceof JSONString
|
||||
|| object instanceof JSONString || object instanceof String
|
||||
|| object instanceof Byte || object instanceof Character
|
||||
|| object instanceof Short || object instanceof Integer
|
||||
|| object instanceof Long || object instanceof Boolean
|
||||
|| object instanceof Float || object instanceof Double
|
||||
|| object instanceof String || object instanceof BigInteger
|
||||
|| object instanceof BigDecimal || object instanceof Enum) {
|
||||
|| object instanceof BigInteger || object instanceof BigDecimal
|
||||
|| object instanceof Enum) {
|
||||
return object;
|
||||
}
|
||||
|
||||
@ -3027,24 +3114,4 @@ public class JSONObject {
|
||||
"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";
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,11 @@ public class JSONParserConfiguration extends ParserConfiguration {
|
||||
* Used to indicate whether to overwrite duplicate key or not.
|
||||
*/
|
||||
private boolean overwriteDuplicateKey;
|
||||
|
||||
/**
|
||||
* Used to indicate whether to convert java null values to JSONObject.NULL or ignoring the entry when converting java maps.
|
||||
*/
|
||||
private boolean useNativeNulls;
|
||||
|
||||
/**
|
||||
* Configuration with the default values.
|
||||
@ -15,6 +20,8 @@ public class JSONParserConfiguration extends ParserConfiguration {
|
||||
public JSONParserConfiguration() {
|
||||
super();
|
||||
this.overwriteDuplicateKey = false;
|
||||
// DO NOT DELETE THE FOLLOWING LINE -- it is used for strictMode testing
|
||||
// this.strictMode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,7 +34,10 @@ public class JSONParserConfiguration extends ParserConfiguration {
|
||||
protected JSONParserConfiguration clone() {
|
||||
JSONParserConfiguration clone = new JSONParserConfiguration();
|
||||
clone.overwriteDuplicateKey = overwriteDuplicateKey;
|
||||
clone.strictMode = strictMode;
|
||||
clone.maxNestingDepth = maxNestingDepth;
|
||||
clone.keepStrings = keepStrings;
|
||||
clone.useNativeNulls = useNativeNulls;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@ -63,6 +73,21 @@ public class JSONParserConfiguration extends ParserConfiguration {
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the parser's behavior when meeting Java null values while converting maps.
|
||||
* If set to true, the parser will put a JSONObject.NULL into the resulting JSONObject.
|
||||
* Or the map entry will be ignored.
|
||||
*
|
||||
* @param useNativeNulls defines if the parser should convert null values in Java maps
|
||||
* @return The existing configuration will not be modified. A new configuration is returned.
|
||||
*/
|
||||
public JSONParserConfiguration withUseNativeNulls(final boolean useNativeNulls) {
|
||||
JSONParserConfiguration clone = this.clone();
|
||||
clone.useNativeNulls = useNativeNulls;
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the strict mode configuration for the JSON parser with default true value
|
||||
@ -102,8 +127,23 @@ public class JSONParserConfiguration extends ParserConfiguration {
|
||||
public boolean isOverwriteDuplicateKey() {
|
||||
return this.overwriteDuplicateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parser's behavior when meeting a null value in a java map, controls whether the parser should
|
||||
* write a JSON entry with a null value (<code>isUseNativeNulls() == true</code>)
|
||||
* or ignore that map entry (<code>isUseNativeNulls() == false</code>).
|
||||
*
|
||||
* @return The <code>useNativeNulls</code> configuration value.
|
||||
*/
|
||||
public boolean isUseNativeNulls() {
|
||||
return this.useNativeNulls;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The parser throws an Exception when strict mode is true and tries to parse invalid JSON characters.
|
||||
* Otherwise, the parser is more relaxed and might tolerate some invalid characters.
|
||||
*
|
||||
* @return the current strict mode setting.
|
||||
*/
|
||||
public boolean isStrictMode() {
|
||||
|
@ -127,7 +127,7 @@ public class JSONPointer {
|
||||
if (pointer == null) {
|
||||
throw new NullPointerException("pointer cannot be null");
|
||||
}
|
||||
if (pointer.isEmpty() || pointer.equals("#")) {
|
||||
if (pointer.isEmpty() || "#".equals(pointer)) {
|
||||
this.refTokens = Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
@ -246,7 +246,7 @@ public class JSONPointer {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder rval = new StringBuilder("");
|
||||
StringBuilder rval = new StringBuilder();
|
||||
for (String token: this.refTokens) {
|
||||
rval.append('/').append(escape(token));
|
||||
}
|
||||
|
@ -38,9 +38,21 @@ public class JSONTokener {
|
||||
/**
|
||||
* Construct a JSONTokener from a Reader. The caller must close the Reader.
|
||||
*
|
||||
* @param reader A reader.
|
||||
* @param reader the source.
|
||||
*/
|
||||
public JSONTokener(Reader reader) {
|
||||
this(reader, new JSONParserConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from a Reader with a given JSONParserConfiguration. The caller must close the Reader.
|
||||
*
|
||||
* @param reader the source.
|
||||
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
|
||||
*
|
||||
*/
|
||||
public JSONTokener(Reader reader, JSONParserConfiguration jsonParserConfiguration) {
|
||||
this.jsonParserConfiguration = jsonParserConfiguration;
|
||||
this.reader = reader.markSupported()
|
||||
? reader
|
||||
: new BufferedReader(reader);
|
||||
@ -53,23 +65,40 @@ public class JSONTokener {
|
||||
this.line = 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
|
||||
* @param inputStream The source.
|
||||
*/
|
||||
public JSONTokener(InputStream inputStream) {
|
||||
this(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
|
||||
this(inputStream, new JSONParserConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
|
||||
* @param inputStream The source.
|
||||
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
|
||||
*/
|
||||
public JSONTokener(InputStream inputStream, JSONParserConfiguration jsonParserConfiguration) {
|
||||
this(new InputStreamReader(inputStream, Charset.forName("UTF-8")), jsonParserConfiguration);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from a string.
|
||||
*
|
||||
* @param s A source string.
|
||||
* @param source A source string.
|
||||
*/
|
||||
public JSONTokener(String s) {
|
||||
this(new StringReader(s));
|
||||
public JSONTokener(String source) {
|
||||
this(new StringReader(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
|
||||
* @param source The source.
|
||||
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
|
||||
*/
|
||||
public JSONTokener(String source, JSONParserConfiguration jsonParserConfiguration) {
|
||||
this(new StringReader(source), jsonParserConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +112,10 @@ public class JSONTokener {
|
||||
/**
|
||||
* Setter
|
||||
* @param jsonParserConfiguration new value for jsonParserConfiguration
|
||||
*
|
||||
* @deprecated method should not be used
|
||||
*/
|
||||
@Deprecated
|
||||
public void setJsonParserConfiguration(JSONParserConfiguration jsonParserConfiguration) {
|
||||
this.jsonParserConfiguration = jsonParserConfiguration;
|
||||
}
|
||||
@ -479,11 +511,21 @@ public class JSONTokener {
|
||||
throw this.syntaxError("Missing value");
|
||||
}
|
||||
Object obj = JSONObject.stringToValue(string);
|
||||
// Strict mode only allows strings with explicit double quotes
|
||||
// if obj is a boolean, look at string
|
||||
if (jsonParserConfiguration != null &&
|
||||
jsonParserConfiguration.isStrictMode() &&
|
||||
obj instanceof String) {
|
||||
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not surrounded by quotes", obj));
|
||||
jsonParserConfiguration.isStrictMode()) {
|
||||
if (obj instanceof Boolean && !"true".equals(string) && !"false".equals(string)) {
|
||||
// Strict mode only allows lowercase true or false
|
||||
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;
|
||||
}
|
||||
|
@ -355,10 +355,20 @@ public class XML {
|
||||
&& TYPE_ATTR.equals(string)) {
|
||||
xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
|
||||
} else if (!nilAttributeFound) {
|
||||
jsonObject.accumulate(string,
|
||||
config.isKeepStrings()
|
||||
? ((String) token)
|
||||
: stringToValue((String) token));
|
||||
Object obj = stringToValue((String) token);
|
||||
if (obj instanceof Boolean) {
|
||||
jsonObject.accumulate(string,
|
||||
config.isKeepBooleanAsString()
|
||||
? ((String) token)
|
||||
: obj);
|
||||
} else if (obj instanceof Number) {
|
||||
jsonObject.accumulate(string,
|
||||
config.isKeepNumberAsString()
|
||||
? ((String) token)
|
||||
: obj);
|
||||
} else {
|
||||
jsonObject.accumulate(string, stringToValue((String) token));
|
||||
}
|
||||
}
|
||||
token = null;
|
||||
} else {
|
||||
@ -407,8 +417,23 @@ public class XML {
|
||||
jsonObject.accumulate(config.getcDataTagName(),
|
||||
stringToValue(string, xmlXsiTypeConverter));
|
||||
} else {
|
||||
jsonObject.accumulate(config.getcDataTagName(),
|
||||
config.isKeepStrings() ? string : stringToValue(string));
|
||||
Object obj = stringToValue((String) token);
|
||||
if (obj instanceof Boolean) {
|
||||
jsonObject.accumulate(config.getcDataTagName(),
|
||||
config.isKeepBooleanAsString()
|
||||
? ((String) token)
|
||||
: obj);
|
||||
} else if (obj instanceof Number) {
|
||||
jsonObject.accumulate(config.getcDataTagName(),
|
||||
config.isKeepNumberAsString()
|
||||
? ((String) token)
|
||||
: obj);
|
||||
} else if (obj == JSONObject.NULL) {
|
||||
jsonObject.accumulate(config.getcDataTagName(),
|
||||
config.isKeepStrings() ? ((String) token) : obj);
|
||||
} else {
|
||||
jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,6 +713,44 @@ public class XML {
|
||||
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML into a
|
||||
* JSONObject. Some information may be lost in this transformation because
|
||||
* JSON is a data format and XML is a document format. XML uses elements,
|
||||
* attributes, and content text, while JSON uses unordered collections of
|
||||
* name/value pairs and arrays of values. JSON does not does not like to
|
||||
* distinguish between elements and attributes. Sequences of similar
|
||||
* elements are represented as JSONArrays. Content text may be placed in a
|
||||
* "content" member. Comments, prologs, DTDs, and <pre>{@code
|
||||
* <[ [ ]]>}</pre>
|
||||
* are ignored.
|
||||
*
|
||||
* All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
|
||||
* numbers but will instead be the exact value as seen in the XML document depending
|
||||
* on how flag is set.
|
||||
* All booleans are converted as strings, for true, false will not be coerced to
|
||||
* booleans but will instead be the exact value as seen in the XML document depending
|
||||
* on how flag is set.
|
||||
*
|
||||
* @param reader The XML source reader.
|
||||
* @param keepNumberAsString If true, then numeric values will not be coerced into
|
||||
* numeric values and will instead be left as strings
|
||||
* @param keepBooleanAsString If true, then boolean values will not be coerced into
|
||||
* * numeric values and will instead be left as strings
|
||||
* @return A JSONObject containing the structured data from the XML string.
|
||||
* @throws JSONException Thrown if there is an errors while parsing the string
|
||||
*/
|
||||
public static JSONObject toJSONObject(Reader reader, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
|
||||
XMLParserConfiguration xmlParserConfiguration = new XMLParserConfiguration();
|
||||
if(keepNumberAsString) {
|
||||
xmlParserConfiguration = xmlParserConfiguration.withKeepNumberAsString(keepNumberAsString);
|
||||
}
|
||||
if(keepBooleanAsString) {
|
||||
xmlParserConfiguration = xmlParserConfiguration.withKeepBooleanAsString(keepBooleanAsString);
|
||||
}
|
||||
return toJSONObject(reader, xmlParserConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML into a
|
||||
* JSONObject. Some information may be lost in this transformation because
|
||||
@ -746,6 +809,38 @@ public class XML {
|
||||
return toJSONObject(new StringReader(string), keepStrings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONObject. Some information may be lost in this transformation because
|
||||
* JSON is a data format and XML is a document format. XML uses elements,
|
||||
* attributes, and content text, while JSON uses unordered collections of
|
||||
* name/value pairs and arrays of values. JSON does not does not like to
|
||||
* distinguish between elements and attributes. Sequences of similar
|
||||
* elements are represented as JSONArrays. Content text may be placed in a
|
||||
* "content" member. Comments, prologs, DTDs, and <pre>{@code
|
||||
* <[ [ ]]>}</pre>
|
||||
* are ignored.
|
||||
*
|
||||
* All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
|
||||
* numbers but will instead be the exact value as seen in the XML document depending
|
||||
* on how flag is set.
|
||||
* All booleans are converted as strings, for true, false will not be coerced to
|
||||
* booleans but will instead be the exact value as seen in the XML document depending
|
||||
* on how flag is set.
|
||||
*
|
||||
* @param string
|
||||
* The source string.
|
||||
* @param keepNumberAsString If true, then numeric values will not be coerced into
|
||||
* numeric values and will instead be left as strings
|
||||
* @param keepBooleanAsString If true, then boolean values will not be coerced into
|
||||
* numeric values and will instead be left as strings
|
||||
* @return A JSONObject containing the structured data from the XML string.
|
||||
* @throws JSONException Thrown if there is an errors while parsing the string
|
||||
*/
|
||||
public static JSONObject toJSONObject(String string, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
|
||||
return toJSONObject(new StringReader(string), keepNumberAsString, keepBooleanAsString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONObject. Some information may be lost in this transformation because
|
||||
|
@ -22,6 +22,16 @@ public class XMLParserConfiguration extends ParserConfiguration {
|
||||
*/
|
||||
// public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512; // We could override
|
||||
|
||||
/**
|
||||
* Allow user to control how numbers are parsed
|
||||
*/
|
||||
private boolean keepNumberAsString;
|
||||
|
||||
/**
|
||||
* Allow user to control how booleans are parsed
|
||||
*/
|
||||
private boolean keepBooleanAsString;
|
||||
|
||||
/** Original Configuration of the XML Parser. */
|
||||
public static final XMLParserConfiguration ORIGINAL
|
||||
= new XMLParserConfiguration();
|
||||
@ -142,7 +152,9 @@ public class XMLParserConfiguration extends ParserConfiguration {
|
||||
*/
|
||||
@Deprecated
|
||||
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
|
||||
super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
|
||||
super(false, DEFAULT_MAXIMUM_NESTING_DEPTH);
|
||||
this.keepNumberAsString = keepStrings;
|
||||
this.keepBooleanAsString = keepStrings;
|
||||
this.cDataTagName = cDataTagName;
|
||||
this.convertNilAttributeToNull = convertNilAttributeToNull;
|
||||
}
|
||||
@ -163,8 +175,10 @@ public class XMLParserConfiguration extends ParserConfiguration {
|
||||
*/
|
||||
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
|
||||
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList,
|
||||
final int maxNestingDepth, final boolean closeEmptyTag) {
|
||||
super(keepStrings, maxNestingDepth);
|
||||
final int maxNestingDepth, final boolean closeEmptyTag, final boolean keepNumberAsString, final boolean keepBooleanAsString) {
|
||||
super(false, maxNestingDepth);
|
||||
this.keepNumberAsString = keepNumberAsString;
|
||||
this.keepBooleanAsString = keepBooleanAsString;
|
||||
this.cDataTagName = cDataTagName;
|
||||
this.convertNilAttributeToNull = convertNilAttributeToNull;
|
||||
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
|
||||
@ -189,7 +203,9 @@ public class XMLParserConfiguration extends ParserConfiguration {
|
||||
this.xsiTypeMap,
|
||||
this.forceList,
|
||||
this.maxNestingDepth,
|
||||
this.closeEmptyTag
|
||||
this.closeEmptyTag,
|
||||
this.keepNumberAsString,
|
||||
this.keepBooleanAsString
|
||||
);
|
||||
config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
|
||||
return config;
|
||||
@ -207,7 +223,43 @@ public class XMLParserConfiguration extends ParserConfiguration {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public XMLParserConfiguration withKeepStrings(final boolean newVal) {
|
||||
return super.withKeepStrings(newVal);
|
||||
XMLParserConfiguration newConfig = this.clone();
|
||||
newConfig.keepStrings = newVal;
|
||||
newConfig.keepNumberAsString = newVal;
|
||||
newConfig.keepBooleanAsString = newVal;
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* When parsing the XML into JSON, specifies if numbers should be kept as strings (<code>1</code>), or if
|
||||
* they should try to be guessed into JSON values (numeric, boolean, string)
|
||||
*
|
||||
* @param newVal
|
||||
* new value to use for the <code>keepNumberAsString</code> configuration option.
|
||||
*
|
||||
* @return The existing configuration will not be modified. A new configuration is returned.
|
||||
*/
|
||||
public XMLParserConfiguration withKeepNumberAsString(final boolean newVal) {
|
||||
XMLParserConfiguration newConfig = this.clone();
|
||||
newConfig.keepNumberAsString = newVal;
|
||||
newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* When parsing the XML into JSON, specifies if booleans should be kept as strings (<code>true</code>), or if
|
||||
* they should try to be guessed into JSON values (numeric, boolean, string)
|
||||
*
|
||||
* @param newVal
|
||||
* new value to use for the <code>withKeepBooleanAsString</code> configuration option.
|
||||
*
|
||||
* @return The existing configuration will not be modified. A new configuration is returned.
|
||||
*/
|
||||
public XMLParserConfiguration withKeepBooleanAsString(final boolean newVal) {
|
||||
XMLParserConfiguration newConfig = this.clone();
|
||||
newConfig.keepBooleanAsString = newVal;
|
||||
newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,6 +273,26 @@ public class XMLParserConfiguration extends ParserConfiguration {
|
||||
return this.cDataTagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* When parsing the XML into JSONML, specifies if numbers should be kept as strings (<code>true</code>), or if
|
||||
* they should try to be guessed into JSON values (numeric, boolean, string).
|
||||
*
|
||||
* @return The <code>keepStrings</code> configuration value.
|
||||
*/
|
||||
public boolean isKeepNumberAsString() {
|
||||
return this.keepNumberAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* When parsing the XML into JSONML, specifies if booleans should be kept as strings (<code>true</code>), or if
|
||||
* they should try to be guessed into JSON values (numeric, boolean, string).
|
||||
*
|
||||
* @return The <code>keepStrings</code> configuration value.
|
||||
*/
|
||||
public boolean isKeepBooleanAsString() {
|
||||
return this.keepBooleanAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
|
||||
* been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
|
||||
|
@ -168,6 +168,33 @@ public class CDLTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Csv parsing skip last row if last field of this row is empty #943
|
||||
*/
|
||||
@Test
|
||||
public void csvParsingCatchesLastRow(){
|
||||
String data = "Field 1,Field 2,Field 3\n" +
|
||||
"value11,value12,\n" +
|
||||
"value21,value22,";
|
||||
|
||||
JSONArray jsonArray = CDL.toJSONArray(data);
|
||||
|
||||
JSONArray expectedJsonArray = new JSONArray();
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("Field 1", "value11");
|
||||
jsonObject.put("Field 2", "value12");
|
||||
jsonObject.put("Field 3", "");
|
||||
expectedJsonArray.put(jsonObject);
|
||||
|
||||
jsonObject = new JSONObject();
|
||||
jsonObject.put("Field 1", "value21");
|
||||
jsonObject.put("Field 2", "value22");
|
||||
jsonObject.put("Field 3", "");
|
||||
expectedJsonArray.put(jsonObject);
|
||||
|
||||
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that there is no error for a single escaped quote within a properly embedded quote.
|
||||
*/
|
||||
|
107
src/test/java/org/json/junit/HTTPTokenerTest.java
Normal file
107
src/test/java/org/json/junit/HTTPTokenerTest.java
Normal file
@ -0,0 +1,107 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@ -227,6 +228,19 @@ public class JSONArrayTest {
|
||||
Util.checkJSONArrayMaps(jaRaw);
|
||||
Util.checkJSONArrayMaps(jaInt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonArrayByListWithNestedNullValue() {
|
||||
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
|
||||
Map<String, Object> sub = new HashMap<String, Object>();
|
||||
sub.put("nullKey", null);
|
||||
list.add(sub);
|
||||
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
|
||||
JSONArray jsonArray = new JSONArray(list, parserConfiguration);
|
||||
JSONObject subObject = jsonArray.getJSONObject(0);
|
||||
assertTrue(subObject.has("nullKey"));
|
||||
assertEquals(JSONObject.NULL, subObject.get("nullKey"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests consecutive calls to putAll with array and collection.
|
||||
@ -476,13 +490,18 @@ public class JSONArrayTest {
|
||||
*/
|
||||
@Test
|
||||
public void unquotedText() {
|
||||
String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
|
||||
List<Object> expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
|
||||
|
||||
// Test should fail if default strictMode is true, pass if false
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
System.out.println("Skipping JSONArrayTest unquotedText() when strictMode default is true");
|
||||
} else {
|
||||
String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
|
||||
try {
|
||||
JSONArray jsonArray = new JSONArray(str);
|
||||
List<Object> expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
|
||||
assertEquals("Expected to throw exception due to invalid string", true, false);
|
||||
} catch (JSONException e) { }
|
||||
} else {
|
||||
JSONArray jsonArray = new JSONArray(str);
|
||||
assertEquals(expected, jsonArray.toList());
|
||||
}
|
||||
}
|
||||
@ -1509,6 +1528,14 @@ public class JSONArrayTest {
|
||||
new JSONArray(array);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrictModeJSONTokener_expectException(){
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
|
||||
JSONTokener tokener = new JSONTokener("[\"value\"]invalidCharacters", jsonParserConfiguration);
|
||||
|
||||
assertThrows(JSONException.class, () -> { new JSONArray(tokener); });
|
||||
}
|
||||
|
||||
public static ArrayList<Object> buildNestedArray(int maxDepth) {
|
||||
if (maxDepth <= 0) {
|
||||
return new ArrayList<>();
|
||||
|
@ -218,11 +218,16 @@ public class JSONObjectTest {
|
||||
*/
|
||||
@Test
|
||||
public void unquotedText() {
|
||||
String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
|
||||
|
||||
// Test should fail if default strictMode is true, pass if false
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
System.out.println("Skipping JSONObjectTest unquotedText() when strictMode default is true");
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
assertEquals("Expected to throw exception due to invalid string", true, false);
|
||||
} catch (JSONException e) { }
|
||||
} else {
|
||||
String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
String textStr = jsonObject.toString();
|
||||
assertTrue("expected key1", textStr.contains("\"key1\""));
|
||||
@ -611,6 +616,46 @@ public class JSONObjectTest {
|
||||
assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
|
||||
Util.checkJSONObjectMaps(jsonObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectByMapWithNullValueAndParserConfiguration() {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("nullKey", null);
|
||||
|
||||
// by default, null values are ignored
|
||||
JSONObject obj1 = new JSONObject(map);
|
||||
assertTrue("expected null value to be ignored by default", obj1.isEmpty());
|
||||
|
||||
// if configured, null values are written as such into the JSONObject.
|
||||
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
|
||||
JSONObject obj2 = new JSONObject(map, parserConfiguration);
|
||||
assertFalse("expected null value to accepted when configured", obj2.isEmpty());
|
||||
assertTrue(obj2.has("nullKey"));
|
||||
assertEquals(JSONObject.NULL, obj2.get("nullKey"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectByMapWithNestedNullValueAndParserConfiguration() {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
Map<String, Object> nestedMap = new HashMap<String, Object>();
|
||||
nestedMap.put("nullKey", null);
|
||||
map.put("nestedMap", nestedMap);
|
||||
List<Map<String, Object>> nestedList = new ArrayList<Map<String,Object>>();
|
||||
nestedList.add(nestedMap);
|
||||
map.put("nestedList", nestedList);
|
||||
|
||||
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
|
||||
JSONObject jsonObject = new JSONObject(map, parserConfiguration);
|
||||
|
||||
JSONObject nestedObject = jsonObject.getJSONObject("nestedMap");
|
||||
assertTrue(nestedObject.has("nullKey"));
|
||||
assertEquals(JSONObject.NULL, nestedObject.get("nullKey"));
|
||||
|
||||
JSONArray nestedArray = jsonObject.getJSONArray("nestedList");
|
||||
assertEquals(1, nestedArray.length());
|
||||
assertTrue(nestedArray.getJSONObject(0).has("nullKey"));
|
||||
assertEquals(JSONObject.NULL, nestedArray.getJSONObject(0).get("nullKey"));
|
||||
}
|
||||
|
||||
/**
|
||||
* JSONObject built from a bean. In this case all but one of the
|
||||
@ -1074,24 +1119,29 @@ public class JSONObjectTest {
|
||||
*/
|
||||
@Test
|
||||
public void jsonInvalidNumberValues() {
|
||||
// Number-notations supported by Java and invalid as JSON
|
||||
String str =
|
||||
"{" +
|
||||
"\"hexNumber\":-0x123," +
|
||||
"\"tooManyZeros\":00," +
|
||||
"\"negativeInfinite\":-Infinity," +
|
||||
"\"negativeNaN\":-NaN," +
|
||||
"\"negativeFraction\":-.01," +
|
||||
"\"tooManyZerosFraction\":00.001," +
|
||||
"\"negativeHexFloat\":-0x1.fffp1," +
|
||||
"\"hexFloat\":0x1.0P-1074," +
|
||||
"\"floatIdentifier\":0.1f," +
|
||||
"\"doubleIdentifier\":0.1d" +
|
||||
"}";
|
||||
|
||||
// Test should fail if default strictMode is true, pass if false
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
System.out.println("Skipping JSONObjectTest jsonInvalidNumberValues() when strictMode default is true");
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
assertEquals("Expected to throw exception due to invalid string", true, false);
|
||||
} catch (JSONException e) { }
|
||||
} else {
|
||||
// Number-notations supported by Java and invalid as JSON
|
||||
String str =
|
||||
"{" +
|
||||
"\"hexNumber\":-0x123," +
|
||||
"\"tooManyZeros\":00," +
|
||||
"\"negativeInfinite\":-Infinity," +
|
||||
"\"negativeNaN\":-NaN," +
|
||||
"\"negativeFraction\":-.01," +
|
||||
"\"tooManyZerosFraction\":00.001," +
|
||||
"\"negativeHexFloat\":-0x1.fffp1," +
|
||||
"\"hexFloat\":0x1.0P-1074," +
|
||||
"\"floatIdentifier\":0.1f," +
|
||||
"\"doubleIdentifier\":0.1d" +
|
||||
"}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
Object obj;
|
||||
obj = jsonObject.get("hexNumber");
|
||||
@ -2266,332 +2316,417 @@ public class JSONObjectTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explore how JSONObject handles parsing errors.
|
||||
*/
|
||||
@SuppressWarnings({"boxing", "unused"})
|
||||
@Test
|
||||
public void jsonObjectParsingErrors() {
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
System.out.println("Skipping JSONObjectTest jaonObjectParsingErrors() when strictMode default is true");
|
||||
} else {
|
||||
try {
|
||||
// does not start with '{'
|
||||
String str = "abc";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"A JSONObject text must begin with '{' at 1 [character 2 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// does not end with '}'
|
||||
String str = "{";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"A JSONObject text must end with '}' at 1 [character 2 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// key with no ':'
|
||||
String str = "{\"myKey\" = true}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Expected a ':' after a key at 10 [character 11 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// entries with no ',' separator
|
||||
String str = "{\"myKey\":true \"myOtherKey\":false}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Expected a ',' or '}' at 15 [character 16 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// key is a nested map
|
||||
String str = "{{\"foo\": \"bar\"}: \"baz\"}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Missing value at 1 [character 2 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// key is a nested array containing a map
|
||||
String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Missing value at 9 [character 10 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// key contains }
|
||||
String str = "{foo}: 2}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Expected a ':' after a key at 5 [character 6 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// key contains ]
|
||||
String str = "{foo]: 2}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Expected a ':' after a key at 5 [character 6 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// \0 after ,
|
||||
String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"A JSONObject text must end with '}' at 15 [character 16 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// append to wrong key
|
||||
String str = "{\"myKey\":true, \"myOtherKey\":false}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
jsonObject.append("myKey", "hello");
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"JSONObject[\"myKey\"] is not a JSONArray (null).",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// increment wrong key
|
||||
String str = "{\"myKey\":true, \"myOtherKey\":false}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
jsonObject.increment("myKey");
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Unable to increment [\"myKey\"].",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// invalid key
|
||||
String str = "{\"myKey\":true, \"myOtherKey\":false}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
jsonObject.get(null);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Null key.",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// invalid numberToString()
|
||||
JSONObject.numberToString((Number) null);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Null pointer",
|
||||
e.getMessage());
|
||||
}
|
||||
public void parsingErrorTrailingCurlyBrace () {
|
||||
try {
|
||||
// does not end with '}'
|
||||
String str = "{";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"A JSONObject text must end with '}' at 1 [character 2 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// multiple putOnce key
|
||||
JSONObject jsonObject = new JSONObject("{}");
|
||||
jsonObject.putOnce("hello", "world");
|
||||
jsonObject.putOnce("hello", "world!");
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertTrue("", true);
|
||||
}
|
||||
try {
|
||||
// test validity of invalid double
|
||||
JSONObject.testValidity(Double.NaN);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertTrue("", true);
|
||||
}
|
||||
try {
|
||||
// test validity of invalid float
|
||||
JSONObject.testValidity(Float.NEGATIVE_INFINITY);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertTrue("", true);
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 0)
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr03\":\"value-04\"\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 0) holding an object
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr03\": {"
|
||||
+ " \"attr04-01\":\"value-04-01\",n"
|
||||
+ " \"attr04-02\":\"value-04-02\",n"
|
||||
+ " \"attr04-03\":\"value-04-03\"n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 0) holding an array
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr03\": [\n"
|
||||
+ " {"
|
||||
+ " \"attr04-01\":\"value-04-01\",n"
|
||||
+ " \"attr04-02\":\"value-04-02\",n"
|
||||
+ " \"attr04-03\":\"value-04-03\"n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 1)
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr04\": {\n"
|
||||
+ " \"attr04-01\":\"value04-01\",\n"
|
||||
+ " \"attr04-02\":\"value04-02\",\n"
|
||||
+ " \"attr04-03\":\"value04-03\",\n"
|
||||
+ " \"attr04-03\":\"value04-04\"\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 1) holding an object
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr04\": {\n"
|
||||
+ " \"attr04-01\":\"value04-01\",\n"
|
||||
+ " \"attr04-02\":\"value04-02\",\n"
|
||||
+ " \"attr04-03\":\"value04-03\",\n"
|
||||
+ " \"attr04-03\": {\n"
|
||||
+ " \"attr04-04-01\":\"value04-04-01\",\n"
|
||||
+ " \"attr04-04-02\":\"value04-04-02\",\n"
|
||||
+ " \"attr04-04-03\":\"value04-04-03\",\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 1) holding an array
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr04\": {\n"
|
||||
+ " \"attr04-01\":\"value04-01\",\n"
|
||||
+ " \"attr04-02\":\"value04-02\",\n"
|
||||
+ " \"attr04-03\":\"value04-03\",\n"
|
||||
+ " \"attr04-03\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"attr04-04-01\":\"value04-04-01\",\n"
|
||||
+ " \"attr04-04-02\":\"value04-04-02\",\n"
|
||||
+ " \"attr04-04-03\":\"value04-04-03\",\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key in object (level 0) within an array
|
||||
String str = "[\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr01\":\"value-02\"\n"
|
||||
+ " }\n"
|
||||
+ "]";
|
||||
new JSONArray(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr01\" at 124 [character 17 line 8]",
|
||||
e.getMessage());
|
||||
}
|
||||
try {
|
||||
// test exception message when including a duplicate key in object (level 1) within an array
|
||||
String str = "[\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\": {\n"
|
||||
+ " \"attr02-01\":\"value-02-01\",\n"
|
||||
+ " \"attr02-02\":\"value-02-02\"\n"
|
||||
+ " }\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\": {\n"
|
||||
+ " \"attr02-01\":\"value-02-01\",\n"
|
||||
+ " \"attr02-01\":\"value-02-02\"\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "]";
|
||||
new JSONArray(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
|
||||
e.getMessage());
|
||||
}
|
||||
@Test
|
||||
public void parsingErrorInitialCurlyBrace() {
|
||||
try {
|
||||
// does not start with '{'
|
||||
String str = "abc";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"A JSONObject text must begin with '{' at 1 [character 2 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorNoColon() {
|
||||
try {
|
||||
// key with no ':'
|
||||
String str = "{\"myKey\" = true}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Expected a ':' after a key at 10 [character 11 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorNoCommaSeparator() {
|
||||
try {
|
||||
// entries with no ',' separator
|
||||
String str = "{\"myKey\":true \"myOtherKey\":false}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Expected a ',' or '}' at 15 [character 16 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorKeyIsNestedMap() {
|
||||
try {
|
||||
// key is a nested map
|
||||
String str = "{{\"foo\": \"bar\"}: \"baz\"}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Missing value at 1 [character 2 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorKeyIsNestedArrayWithMap() {
|
||||
try {
|
||||
// key is a nested array containing a map
|
||||
String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Missing value at 9 [character 10 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorKeyContainsCurlyBrace() {
|
||||
try {
|
||||
// key contains }
|
||||
String str = "{foo}: 2}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
// assertEquals("Expecting an exception message",
|
||||
// "Expected a ':' after a key at 5 [character 6 line 1]",
|
||||
// e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorKeyContainsSquareBrace() {
|
||||
try {
|
||||
// key contains ]
|
||||
String str = "{foo]: 2}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
// assertEquals("Expecting an exception message",
|
||||
// "Expected a ':' after a key at 5 [character 6 line 1]",
|
||||
// e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorKeyContainsBinaryZero() {
|
||||
try {
|
||||
// \0 after ,
|
||||
String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
|
||||
assertNull("Expected an exception", new JSONObject(str));
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"A JSONObject text must end with '}' at 15 [character 16 line 1]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorAppendToWrongValue() {
|
||||
try {
|
||||
// append to wrong value
|
||||
String str = "{\"myKey\":true, \"myOtherKey\":false}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
jsonObject.append("myKey", "hello");
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"JSONObject[\"myKey\"] is not a JSONArray (null).",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorIncrementWrongValue() {
|
||||
try {
|
||||
// increment wrong value
|
||||
String str = "{\"myKey\":true, \"myOtherKey\":false}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
jsonObject.increment("myKey");
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Unable to increment [\"myKey\"].",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void parsingErrorInvalidKey() {
|
||||
try {
|
||||
// invalid key
|
||||
String str = "{\"myKey\":true, \"myOtherKey\":false}";
|
||||
JSONObject jsonObject = new JSONObject(str);
|
||||
jsonObject.get(null);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Null key.",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorNumberToString() {
|
||||
try {
|
||||
// invalid numberToString()
|
||||
JSONObject.numberToString((Number) null);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an exception message",
|
||||
"Null pointer",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorPutOnceDuplicateKey() {
|
||||
try {
|
||||
// multiple putOnce key
|
||||
JSONObject jsonObject = new JSONObject("{}");
|
||||
jsonObject.putOnce("hello", "world");
|
||||
jsonObject.putOnce("hello", "world!");
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertTrue("", true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorInvalidDouble() {
|
||||
try {
|
||||
// test validity of invalid double
|
||||
JSONObject.testValidity(Double.NaN);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertTrue("", true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorInvalidFloat() {
|
||||
try {
|
||||
// test validity of invalid float
|
||||
JSONObject.testValidity(Float.NEGATIVE_INFINITY);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertTrue("", true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorDuplicateKeyException() {
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 0)
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr03\":\"value-04\"\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorNestedDuplicateKeyException() {
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 0) holding an object
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr03\": {"
|
||||
+ " \"attr04-01\":\"value-04-01\",n"
|
||||
+ " \"attr04-02\":\"value-04-02\",n"
|
||||
+ " \"attr04-03\":\"value-04-03\"n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorNestedDuplicateKeyWithArrayException() {
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 0) holding an array
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr03\": [\n"
|
||||
+ " {"
|
||||
+ " \"attr04-01\":\"value-04-01\",n"
|
||||
+ " \"attr04-02\":\"value-04-02\",n"
|
||||
+ " \"attr04-03\":\"value-04-03\"n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorDuplicateKeyWithinNestedDictExceptionMessage() {
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 1)
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr04\": {\n"
|
||||
+ " \"attr04-01\":\"value04-01\",\n"
|
||||
+ " \"attr04-02\":\"value04-02\",\n"
|
||||
+ " \"attr04-03\":\"value04-03\",\n"
|
||||
+ " \"attr04-03\":\"value04-04\"\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorDuplicateKeyDoubleNestedDictExceptionMessage() {
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 1) holding an
|
||||
// object
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr04\": {\n"
|
||||
+ " \"attr04-01\":\"value04-01\",\n"
|
||||
+ " \"attr04-02\":\"value04-02\",\n"
|
||||
+ " \"attr04-03\":\"value04-03\",\n"
|
||||
+ " \"attr04-03\": {\n"
|
||||
+ " \"attr04-04-01\":\"value04-04-01\",\n"
|
||||
+ " \"attr04-04-02\":\"value04-04-02\",\n"
|
||||
+ " \"attr04-04-03\":\"value04-04-03\",\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorDuplicateKeyNestedWithArrayExceptionMessage() {
|
||||
try {
|
||||
// test exception message when including a duplicate key (level 1) holding an
|
||||
// array
|
||||
String str = "{\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\",\n"
|
||||
+ " \"attr03\":\"value-03\",\n"
|
||||
+ " \"attr04\": {\n"
|
||||
+ " \"attr04-01\":\"value04-01\",\n"
|
||||
+ " \"attr04-02\":\"value04-02\",\n"
|
||||
+ " \"attr04-03\":\"value04-03\",\n"
|
||||
+ " \"attr04-03\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"attr04-04-01\":\"value04-04-01\",\n"
|
||||
+ " \"attr04-04-02\":\"value04-04-02\",\n"
|
||||
+ " \"attr04-04-03\":\"value04-04-03\",\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
new JSONObject(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorDuplicateKeyWithinArrayExceptionMessage() {
|
||||
try {
|
||||
// test exception message when including a duplicate key in object (level 0)
|
||||
// within an array
|
||||
String str = "[\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\":\"value-02\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr01\":\"value-02\"\n"
|
||||
+ " }\n"
|
||||
+ "]";
|
||||
new JSONArray(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr01\" at 124 [character 17 line 8]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingErrorDuplicateKeyDoubleNestedWithinArrayExceptionMessage() {
|
||||
try {
|
||||
// test exception message when including a duplicate key in object (level 1)
|
||||
// within an array
|
||||
String str = "[\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\": {\n"
|
||||
+ " \"attr02-01\":\"value-02-01\",\n"
|
||||
+ " \"attr02-02\":\"value-02-02\"\n"
|
||||
+ " }\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"attr01\":\"value-01\",\n"
|
||||
+ " \"attr02\": {\n"
|
||||
+ " \"attr02-01\":\"value-02-01\",\n"
|
||||
+ " \"attr02-01\":\"value-02-02\"\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "]";
|
||||
new JSONArray(str);
|
||||
fail("Expected an exception");
|
||||
} catch (JSONException e) {
|
||||
assertEquals("Expecting an expection message",
|
||||
"Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3853,6 +3988,65 @@ public class JSONObjectTest {
|
||||
assertEquals(j3.getString("hex6"), "0011");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStrictModeJSONTokener_expectException(){
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
|
||||
JSONTokener tokener = new JSONTokener("{\"key\":\"value\"}invalidCharacters", jsonParserConfiguration);
|
||||
|
||||
assertThrows(JSONException.class, () -> { new JSONObject(tokener); });
|
||||
}
|
||||
|
||||
@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
|
||||
*
|
||||
@ -3867,5 +4061,37 @@ public class JSONObjectTest {
|
||||
nestedMap.put("t", buildNestedMap(maxDepth - 1));
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONParserConfiguration;
|
||||
import org.json.JSONTokener;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -14,7 +15,10 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class JSONParserConfigurationTest {
|
||||
private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
|
||||
@ -32,6 +36,32 @@ public class JSONParserConfigurationTest {
|
||||
assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void strictModeIsCloned(){
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
|
||||
.withStrictMode(true)
|
||||
.withMaxNestingDepth(12);
|
||||
|
||||
assertTrue(jsonParserConfiguration.isStrictMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxNestingDepthIsCloned(){
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
|
||||
.<JSONParserConfiguration>withKeepStrings(true)
|
||||
.withStrictMode(true);
|
||||
|
||||
assertTrue(jsonParserConfiguration.isKeepStrings());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useNativeNullsIsCloned() {
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
|
||||
.withUseNativeNulls(true)
|
||||
.withStrictMode(true);
|
||||
assertTrue(jsonParserConfiguration.isUseNativeNulls());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyDuplicateKeyThenMaxDepth() {
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
|
||||
@ -490,6 +520,40 @@ public class JSONParserConfigurationTest {
|
||||
je.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingJSONTokener_shouldThrowJSONException() {
|
||||
JSONException exception = assertThrows(JSONException.class, () -> {
|
||||
new JSONObject(new JSONTokener("{\"key\":\"value\"} invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
|
||||
});
|
||||
|
||||
assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingString_shouldThrowJSONException() {
|
||||
JSONException exception = assertThrows(JSONException.class, () -> {
|
||||
new JSONObject("{\"key\":\"value\"} invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
|
||||
});
|
||||
assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingJSONTokener_shouldThrowJSONException() {
|
||||
JSONException exception = assertThrows(JSONException.class, () -> {
|
||||
new JSONArray(new JSONTokener("[\"value\"] invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
|
||||
});
|
||||
|
||||
assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingString_shouldThrowJSONException() {
|
||||
JSONException exception = assertThrows(JSONException.class, () -> {
|
||||
new JSONArray("[\"value\"] invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
|
||||
});
|
||||
assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
|
||||
* this class.
|
||||
|
@ -16,10 +16,7 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONTokener;
|
||||
import org.json.*;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
@ -98,7 +95,17 @@ public class JSONTokenerTest {
|
||||
checkValid(" [] ",JSONArray.class);
|
||||
checkValid("[1,2]",JSONArray.class);
|
||||
checkValid("\n\n[1,2]\n\n",JSONArray.class);
|
||||
checkValid("1 2", String.class);
|
||||
|
||||
// Test should fail if default strictMode is true, pass if false
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
try {
|
||||
checkValid("1 2", String.class);
|
||||
assertEquals("Expected to throw exception due to invalid string", true, false);
|
||||
} catch (JSONException e) { }
|
||||
} else {
|
||||
checkValid("1 2", String.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -325,4 +332,42 @@ public class JSONTokenerTest {
|
||||
assertEquals("Stream closed", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInput_JSONObject_withoutStrictModel_shouldParseInput() {
|
||||
String input = "{\"invalidInput\": [],}";
|
||||
JSONTokener tokener = new JSONTokener(input);
|
||||
|
||||
// Test should fail if default strictMode is true, pass if false
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
try {
|
||||
Object value = tokener.nextValue();
|
||||
assertEquals(new JSONObject(input).toString(), value.toString());
|
||||
assertEquals("Expected to throw exception due to invalid string", true, false);
|
||||
} catch (JSONException e) { }
|
||||
} else {
|
||||
Object value = tokener.nextValue();
|
||||
assertEquals(new JSONObject(input).toString(), value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInput_JSONArray_withoutStrictModel_shouldParseInput() {
|
||||
String input = "[\"invalidInput\",]";
|
||||
JSONTokener tokener = new JSONTokener(input);
|
||||
|
||||
// Test should fail if default strictMode is true, pass if false
|
||||
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
|
||||
if (jsonParserConfiguration.isStrictMode()) {
|
||||
try {
|
||||
Object value = tokener.nextValue();
|
||||
assertEquals(new JSONArray(input).toString(), value.toString());
|
||||
assertEquals("Expected to throw exception due to invalid string", true, false);
|
||||
} catch (JSONException e) { }
|
||||
} else {
|
||||
Object value = tokener.nextValue();
|
||||
assertEquals(new JSONArray(input).toString(), value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -574,15 +574,18 @@ public class XMLConfigurationTest {
|
||||
XMLParserConfiguration keepStringsAndCloseEmptyTag = keepStrings.withCloseEmptyTag(true);
|
||||
XMLParserConfiguration keepDigits = keepStringsAndCloseEmptyTag.withKeepStrings(false);
|
||||
XMLParserConfiguration keepDigitsAndNoCloseEmptyTag = keepDigits.withCloseEmptyTag(false);
|
||||
assertTrue(keepStrings.isKeepStrings());
|
||||
assertTrue(keepStrings.isKeepNumberAsString());
|
||||
assertTrue(keepStrings.isKeepBooleanAsString());
|
||||
assertFalse(keepStrings.isCloseEmptyTag());
|
||||
assertTrue(keepStringsAndCloseEmptyTag.isKeepStrings());
|
||||
assertTrue(keepStringsAndCloseEmptyTag.isKeepNumberAsString());
|
||||
assertTrue(keepStringsAndCloseEmptyTag.isKeepBooleanAsString());
|
||||
assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag());
|
||||
assertFalse(keepDigits.isKeepStrings());
|
||||
assertFalse(keepDigits.isKeepNumberAsString());
|
||||
assertFalse(keepDigits.isKeepBooleanAsString());
|
||||
assertTrue(keepDigits.isCloseEmptyTag());
|
||||
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepStrings());
|
||||
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepNumberAsString());
|
||||
assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepBooleanAsString());
|
||||
assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -767,6 +770,67 @@ public class XMLConfigurationTest {
|
||||
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON string lost leading zero and converted "True" to true.
|
||||
*/
|
||||
@Test
|
||||
public void testToJSONArray_jsonOutput_withKeepNumberAsString() {
|
||||
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><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.
|
||||
*/
|
||||
|
81
src/test/java/org/json/junit/XMLTokenerTest.java
Normal file
81
src/test/java/org/json/junit/XMLTokenerTest.java
Normal file
@ -0,0 +1,81 @@
|
||||
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 & 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"
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user