11package org .json ;
22
3+ import java .io .Closeable ;
4+
35/*
46 Copyright (c) 2002 JSON.org
57
@@ -28,6 +30,7 @@ of this software and associated documentation files (the "Software"), to deal
2830import java .io .StringWriter ;
2931import java .io .Writer ;
3032import java .lang .reflect .Field ;
33+ import java .lang .reflect .InvocationTargetException ;
3134import java .lang .reflect .Method ;
3235import java .lang .reflect .Modifier ;
3336import java .math .BigDecimal ;
@@ -228,7 +231,21 @@ public JSONObject(JSONTokener x) throws JSONException {
228231 if (c != ':' ) {
229232 throw x .syntaxError ("Expected a ':' after a key" );
230233 }
231- this .putOnce (key , x .nextValue ());
234+
235+ // Use syntaxError(..) to include error location
236+
237+ if (key != null ) {
238+ // Check if key exists
239+ if (this .opt (key ) != null ) {
240+ // key already exists
241+ throw x .syntaxError ("Duplicate key \" " + key + "\" " );
242+ }
243+ // Only add value if non-null
244+ Object value = x .nextValue ();
245+ if (value !=null ) {
246+ this .put (key , value );
247+ }
248+ }
232249
233250 // Pairs are separated by ','.
234251
@@ -276,16 +293,19 @@ public JSONObject(Map<?, ?> m) {
276293 * <code>"is"</code> followed by an uppercase letter, the method is invoked,
277294 * and a key and the value returned from the getter method are put into the
278295 * new JSONObject.
279- *
296+ * <p>
280297 * The key is formed by removing the <code>"get"</code> or <code>"is"</code>
281298 * prefix. If the second remaining character is not upper case, then the
282299 * first character is converted to lower case.
283- *
300+ * <p>
284301 * For example, if an object has a method named <code>"getName"</code>, and
285302 * if the result of calling <code>object.getName()</code> is
286303 * <code>"Larry Fine"</code>, then the JSONObject will contain
287304 * <code>"name": "Larry Fine"</code>.
288- *
305+ * <p>
306+ * Methods that return <code>void</code> as well as <code>static</code>
307+ * methods are ignored.
308+ *
289309 * @param bean
290310 * An object that has getter methods that should be used to make
291311 * a JSONObject.
@@ -1388,6 +1408,15 @@ public String optString(String key, String defaultValue) {
13881408 return NULL .equals (object ) ? defaultValue : object .toString ();
13891409 }
13901410
1411+ /**
1412+ * Populates the internal map of the JSONObject with the bean properties.
1413+ * The bean can not be recursive.
1414+ *
1415+ * @see JSONObject#JSONObject(Object)
1416+ *
1417+ * @param bean
1418+ * the bean
1419+ */
13911420 private void populateMap (Object bean ) {
13921421 Class <?> klass = bean .getClass ();
13931422
@@ -1397,39 +1426,52 @@ private void populateMap(Object bean) {
13971426
13981427 Method [] methods = includeSuperClass ? klass .getMethods () : klass
13991428 .getDeclaredMethods ();
1400- for (int i = 0 ; i < methods .length ; i += 1 ) {
1401- try {
1402- Method method = methods [i ];
1403- if (Modifier .isPublic (method .getModifiers ())) {
1404- String name = method .getName ();
1405- String key = "" ;
1406- if (name .startsWith ("get" )) {
1407- if ("getClass" .equals (name )
1408- || "getDeclaringClass" .equals (name )) {
1409- key = "" ;
1410- } else {
1411- key = name .substring (3 );
1412- }
1413- } else if (name .startsWith ("is" )) {
1414- key = name .substring (2 );
1429+ for (final Method method : methods ) {
1430+ final int modifiers = method .getModifiers ();
1431+ if (Modifier .isPublic (modifiers )
1432+ && !Modifier .isStatic (modifiers )
1433+ && method .getParameterTypes ().length == 0
1434+ && !method .isBridge ()
1435+ && method .getReturnType () != Void .TYPE ) {
1436+ final String name = method .getName ();
1437+ String key ;
1438+ if (name .startsWith ("get" )) {
1439+ if ("getClass" .equals (name ) || "getDeclaringClass" .equals (name )) {
1440+ continue ;
1441+ }
1442+ key = name .substring (3 );
1443+ } else if (name .startsWith ("is" )) {
1444+ key = name .substring (2 );
1445+ } else {
1446+ continue ;
1447+ }
1448+ if (key .length () > 0
1449+ && Character .isUpperCase (key .charAt (0 ))) {
1450+ if (key .length () == 1 ) {
1451+ key = key .toLowerCase (Locale .ROOT );
1452+ } else if (!Character .isUpperCase (key .charAt (1 ))) {
1453+ key = key .substring (0 , 1 ).toLowerCase (Locale .ROOT )
1454+ + key .substring (1 );
14151455 }
1416- if (key .length () > 0
1417- && Character .isUpperCase (key .charAt (0 ))
1418- && method .getParameterTypes ().length == 0 ) {
1419- if (key .length () == 1 ) {
1420- key = key .toLowerCase (Locale .ROOT );
1421- } else if (!Character .isUpperCase (key .charAt (1 ))) {
1422- key = key .substring (0 , 1 ).toLowerCase (Locale .ROOT )
1423- + key .substring (1 );
1424- }
14251456
1426- Object result = method .invoke (bean , (Object []) null );
1457+ try {
1458+ final Object result = method .invoke (bean );
14271459 if (result != null ) {
14281460 this .map .put (key , wrap (result ));
1461+ // we don't use the result anywhere outside of wrap
1462+ // if it's a resource we should be sure to close it after calling toString
1463+ if (result instanceof Closeable ) {
1464+ try {
1465+ ((Closeable )result ).close ();
1466+ } catch (IOException ignore ) {
1467+ }
1468+ }
14291469 }
1470+ } catch (IllegalAccessException ignore ) {
1471+ } catch (IllegalArgumentException ignore ) {
1472+ } catch (InvocationTargetException ignore ) {
14301473 }
14311474 }
1432- } catch (Exception ignore ) {
14331475 }
14341476 }
14351477 }
@@ -1676,7 +1718,7 @@ public Object optQuery(String jsonPointer) {
16761718 * Queries and returns a value from this object using {@code jsonPointer}, or
16771719 * returns null if the query fails due to a missing key.
16781720 *
1679- * @param The JSON pointer
1721+ * @param jsonPointer The JSON pointer
16801722 * @return the queried value or {@code null}
16811723 * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
16821724 */
@@ -2004,9 +2046,10 @@ public JSONArray toJSONArray(JSONArray names) throws JSONException {
20042046 * Make a JSON text of this JSONObject. For compactness, no whitespace is
20052047 * added. If this would not result in a syntactically correct JSON text,
20062048 * then null will be returned instead.
2007- * <p>
2049+ * <p><b>
20082050 * Warning: This method assumes that the data structure is acyclical.
2009- *
2051+ * </b>
2052+ *
20102053 * @return a printable, displayable, portable, transmittable representation
20112054 * of the object, beginning with <code>{</code> <small>(left
20122055 * brace)</small> and ending with <code>}</code> <small>(right
@@ -2023,8 +2066,20 @@ public String toString() {
20232066
20242067 /**
20252068 * Make a pretty-printed JSON text of this JSONObject.
2026- * <p>
2069+ *
2070+ * <p>If <code>indentFactor > 0</code> and the {@link JSONObject}
2071+ * has only one key, then the object will be output on a single line:
2072+ * <pre>{@code {"key": 1}}</pre>
2073+ *
2074+ * <p>If an object has 2 or more keys, then it will be output across
2075+ * multiple lines: <code><pre>{
2076+ * "key1": 1,
2077+ * "key2": "value 2",
2078+ * "key3": 3
2079+ * }</pre></code>
2080+ * <p><b>
20272081 * Warning: This method assumes that the data structure is acyclical.
2082+ * </b>
20282083 *
20292084 * @param indentFactor
20302085 * The number of spaces to add to each level of indentation.
@@ -2130,9 +2185,10 @@ public static Object wrap(Object object) {
21302185 /**
21312186 * Write the contents of the JSONObject as JSON text to a writer. For
21322187 * compactness, no whitespace is added.
2133- * <p>
2188+ * <p><b>
21342189 * Warning: This method assumes that the data structure is acyclical.
2135- *
2190+ * </b>
2191+ *
21362192 * @return The writer.
21372193 * @throws JSONException
21382194 */
@@ -2196,8 +2252,20 @@ static final void indent(Writer writer, int indent) throws IOException {
21962252
21972253 /**
21982254 * Write the contents of the JSONObject as JSON text to a writer.
2199- * <p>
2255+ *
2256+ * <p>If <code>indentFactor > 0</code> and the {@link JSONObject}
2257+ * has only one key, then the object will be output on a single line:
2258+ * <pre>{@code {"key": 1}}</pre>
2259+ *
2260+ * <p>If an object has 2 or more keys, then it will be output across
2261+ * multiple lines: <code><pre>{
2262+ * "key1": 1,
2263+ * "key2": "value 2",
2264+ * "key3": 3
2265+ * }</pre></code>
2266+ * <p><b>
22002267 * Warning: This method assumes that the data structure is acyclical.
2268+ * </b>
22012269 *
22022270 * @param writer
22032271 * Writes the serialized JSON
0 commit comments