mirror of
https://github.com/stleary/JSON-java.git
synced 2025-08-03 11:25:30 -04:00
Compare commits
34 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 |
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
|
||||
|
14
build.gradle
14
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'
|
||||
|
@ -334,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.
|
||||
*
|
||||
@ -180,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -211,45 +200,62 @@ public class JSONObject {
|
||||
*/
|
||||
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
|
||||
this();
|
||||
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();
|
||||
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 x.syntaxError("A JSONObject text must end with '}'");
|
||||
throw jsonTokener.syntaxError("A JSONObject text must end with '}'");
|
||||
case '}':
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
|
||||
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
|
||||
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
return;
|
||||
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 = x.nextClean();
|
||||
c = jsonTokener.nextClean();
|
||||
if (c != ':') {
|
||||
throw x.syntaxError("Expected a ':' after a key");
|
||||
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 x.syntaxError("Duplicate key \"" + key + "\"");
|
||||
throw jsonTokener.syntaxError("Duplicate key \"" + key + "\"");
|
||||
}
|
||||
|
||||
Object value = x.nextValue();
|
||||
Object value = jsonTokener.nextValue();
|
||||
// Only add value if non-null
|
||||
if (value != null) {
|
||||
this.put(key, value);
|
||||
@ -257,33 +263,71 @@ public class JSONObject {
|
||||
}
|
||||
|
||||
// Pairs are separated by ','.
|
||||
if (parseEndOfKeyValuePair(jsonTokener, jsonParserConfiguration, isInitial)) {
|
||||
doneParsing = true;
|
||||
}
|
||||
|
||||
switch (x.nextClean()) {
|
||||
return doneParsing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '}':
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
|
||||
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
|
||||
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
|
||||
}
|
||||
return;
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -401,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());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -674,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);
|
||||
@ -1764,31 +1811,45 @@ 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()) {
|
||||
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) {
|
||||
if (result != null || jsonParserConfiguration.isUseNativeNulls()) {
|
||||
// check cyclic dependency and throw error if needed
|
||||
// the wrap and populateMap combination method is
|
||||
// itself DFS recursive
|
||||
@ -1803,23 +1864,26 @@ public class JSONObject {
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@ -1837,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;
|
||||
@ -1863,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.
|
||||
@ -1906,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 {
|
||||
@ -1964,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 {
|
||||
@ -2750,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;
|
||||
}
|
||||
|
||||
@ -3010,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";
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -511,12 +511,22 @@ 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -428,6 +428,9 @@ public class XML {
|
||||
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));
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
|
||||
}
|
@ -3997,6 +3997,56 @@ public class JSONObjectTest {
|
||||
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
|
||||
*
|
||||
@ -4012,4 +4062,36 @@ public class JSONObjectTest {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -775,8 +775,8 @@ public class XMLConfigurationTest {
|
||||
*/
|
||||
@Test
|
||||
public void testToJSONArray_jsonOutput_withKeepNumberAsString() {
|
||||
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
|
||||
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":true}}");
|
||||
final 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);
|
||||
@ -787,13 +787,25 @@ public class XMLConfigurationTest {
|
||||
*/
|
||||
@Test
|
||||
public void testToJSONArray_jsonOutput_withKeepBooleanAsString() {
|
||||
final String originalXml = "<root><id>01</id><id>1</id><id>00</id><id>0</id><item id=\"01\"/><title>True</title></root>";
|
||||
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":\"True\"}}");
|
||||
final 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
|
||||
*/
|
||||
|
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