Compare commits

..

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

12 changed files with 157 additions and 546 deletions

View File

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

View File

@ -3,10 +3,9 @@
*/ */
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'jacoco' // apply plugin: 'jacoco'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
// for now, publishing to maven is still a manual process
//plugins { //plugins {
// id 'java' // id 'java'
//id 'maven-publish' //id 'maven-publish'
@ -20,17 +19,6 @@ repositories {
} }
} }
// To view the report open build/reports/jacoco/test/html/index.html
jacocoTestReport {
reports {
html.required = true
}
}
test {
finalizedBy jacocoTestReport
}
dependencies { dependencies {
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'com.jayway.jsonpath:json-path:2.9.0' testImplementation 'com.jayway.jsonpath:json-path:2.9.0'

View File

@ -334,11 +334,13 @@ public class JSONArray implements Iterable<Object> {
*/ */
public boolean getBoolean(int index) throws JSONException { public boolean getBoolean(int index) throws JSONException {
Object object = this.get(index); Object object = this.get(index);
if (Boolean.FALSE.equals(object) if (object.equals(Boolean.FALSE)
|| (object instanceof String && "false".equalsIgnoreCase((String) object))) { || (object instanceof String && ((String) object)
.equalsIgnoreCase("false"))) {
return false; return false;
} else if (Boolean.TRUE.equals(object) } else if (object.equals(Boolean.TRUE)
|| (object instanceof String && "true".equalsIgnoreCase((String) object))) { || (object instanceof String && ((String) object)
.equalsIgnoreCase("true"))) {
return true; return true;
} }
throw wrongValueFormatException(index, "boolean", object, null); throw wrongValueFormatException(index, "boolean", object, null);

View File

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

View File

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

View File

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

View File

@ -511,21 +511,11 @@ public class JSONTokener {
throw this.syntaxError("Missing value"); throw this.syntaxError("Missing value");
} }
Object obj = JSONObject.stringToValue(string); Object obj = JSONObject.stringToValue(string);
// if obj is a boolean, look at string // Strict mode only allows strings with explicit double quotes
if (jsonParserConfiguration != null && if (jsonParserConfiguration != null &&
jsonParserConfiguration.isStrictMode()) { jsonParserConfiguration.isStrictMode() &&
if (obj instanceof Boolean && !"true".equals(string) && !"false".equals(string)) { obj instanceof String) {
// Strict mode only allows lowercase true or false throw this.syntaxError(String.format("Strict mode error: Value '%s' is not surrounded by quotes", obj));
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not lowercase boolean", obj));
}
else if (obj == JSONObject.NULL && !"null".equals(string)) {
// Strint mode only allows lowercase null
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not lowercase null", obj));
}
else if (obj instanceof String) {
// Strict mode only allows strings with explicit double quotes
throw this.syntaxError(String.format("Strict mode error: Value '%s' is not surrounded by quotes", obj));
}
} }
return obj; return obj;
} }

View File

@ -428,9 +428,6 @@ public class XML {
config.isKeepNumberAsString() config.isKeepNumberAsString()
? ((String) token) ? ((String) token)
: obj); : obj);
} else if (obj == JSONObject.NULL) {
jsonObject.accumulate(config.getcDataTagName(),
config.isKeepStrings() ? ((String) token) : obj);
} else { } else {
jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token)); jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token));
} }

View File

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

View File

@ -3997,56 +3997,6 @@ public class JSONObjectTest {
assertThrows(JSONException.class, () -> { new JSONObject(tokener); }); assertThrows(JSONException.class, () -> { new JSONObject(tokener); });
} }
@Test
public void test_strictModeWithMisCasedBooleanOrNullValue(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
try{
new JSONObject("{\"a\":True}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{\"a\":TRUE}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{\"a\":nUlL}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
}
@Test
public void test_strictModeWithInappropriateKey(){
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
// Parsing the following objects should fail
try{
new JSONObject("{true : 3}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{TRUE : 3}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
try{
new JSONObject("{1 : 3}", jsonParserConfiguration);
fail("Expected an exception");
} catch (JSONException e) {
// No action, expected outcome
}
}
/** /**
* Method to build nested map of max maxDepth * Method to build nested map of max maxDepth
* *
@ -4062,36 +4012,4 @@ public class JSONObjectTest {
return nestedMap; return nestedMap;
} }
/**
* Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
* using a custom {@link JSONParserConfiguration} that enables the use of native nulls.
*
* <p>This test ensures that uninitialized fields in the bean are serialized correctly
* into the resulting JSON object, and their keys are present in the JSON string output.</p>
*/
@Test
public void jsonObjectParseNullFieldsWithParserConfiguration() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
RecursiveBean bean = new RecursiveBean(null);
JSONObject jsonObject = new JSONObject(bean, jsonParserConfiguration.withUseNativeNulls(true));
assertTrue("name key should be present", jsonObject.has("name"));
assertTrue("ref key should be present", jsonObject.has("ref"));
assertTrue("ref2 key should be present", jsonObject.has("ref2"));
}
/**
* Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
* without using a custom {@link JSONParserConfiguration}.
*
* <p>This test ensures that uninitialized fields in the bean are not serialized
* into the resulting JSON object, and the object remains empty.</p>
*/
@Test
public void jsonObjectParseNullFieldsWithoutParserConfiguration() {
RecursiveBean bean = new RecursiveBean(null);
JSONObject jsonObject = new JSONObject(bean);
assertTrue("JSONObject should be empty", jsonObject.isEmpty());
}
} }

View File

@ -775,8 +775,8 @@ public class XMLConfigurationTest {
*/ */
@Test @Test
public void testToJSONArray_jsonOutput_withKeepNumberAsString() { 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 String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\",null],\"title\":true}}"); final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":true}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepNumberAsString(true)); new XMLParserConfiguration().withKeepNumberAsString(true));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected); Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
@ -787,25 +787,13 @@ public class XMLConfigurationTest {
*/ */
@Test @Test
public void testToJSONArray_jsonOutput_withKeepBooleanAsString() { 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 String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0,null],\"title\":\"True\"}}"); final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":\"True\"}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
new XMLParserConfiguration().withKeepBooleanAsString(true)); new XMLParserConfiguration().withKeepBooleanAsString(true));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected); 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 keepStrings behavior when setting keepBooleanAsString, keepNumberAsString
*/ */

View File

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