From 9115ada84d75b9262695462b1f814552a907b3f8 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 22 Apr 2012 16:52:45 -0700 Subject: [PATCH] Performance improvments by Valentin Valchev --- JSONArray.java | 138 +++++++++--------- JSONObject.java | 361 ++++++++++++++++++++++-------------------------- 2 files changed, 224 insertions(+), 275 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index 4ae610f..71c277a 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -25,6 +25,7 @@ SOFTWARE. */ import java.io.IOException; +import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Array; import java.util.ArrayList; @@ -60,23 +61,23 @@ import java.util.Map; * accept: * - + * * @author JSON.org - * @version 2011-12-19 + * @version 2012-04-20 */ public class JSONArray { @@ -555,8 +556,8 @@ public class JSONArray { public String optString(int index, String defaultValue) { Object object = this.opt(index); return JSONObject.NULL.equals(object) - ? defaultValue - : object.toString(); + ? defaultValue : object + .toString(); } @@ -834,56 +835,15 @@ public class JSONArray { * @throws JSONException */ public String toString(int indentFactor) throws JSONException { - return this.toString(indentFactor, 0); + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } } - /** - * Make a prettyprinted JSON text of this JSONArray. - * Warning: This method assumes that the data structure is acyclical. - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @param indent The indention of the top level. - * @return a printable, displayable, transmittable - * representation of the array. - * @throws JSONException - */ - String toString(int indentFactor, int indent) throws JSONException { - int len = this.length(); - if (len == 0) { - return "[]"; - } - int i; - StringBuffer sb = new StringBuffer("["); - if (len == 1) { - sb.append(JSONObject.valueToString(this.myArrayList.get(0), - indentFactor, indent)); - } else { - int newindent = indent + indentFactor; - sb.append('\n'); - for (i = 0; i < len; i += 1) { - if (i > 0) { - sb.append(",\n"); - } - for (int j = 0; j < newindent; j += 1) { - sb.append(' '); - } - sb.append(JSONObject.valueToString(this.myArrayList.get(i), - indentFactor, newindent)); - } - sb.append('\n'); - for (i = 0; i < indent; i += 1) { - sb.append(' '); - } - } - sb.append(']'); - return sb.toString(); - } - - - /** - * Write the contents of the JSONArray as JSON text to a writer. - * For compactness, no whitespace is added. + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. *

* Warning: This method assumes that the data structure is acyclical. * @@ -891,25 +851,51 @@ public class JSONArray { * @throws JSONException */ public Writer write(Writer writer) throws JSONException { - try { - boolean b = false; - int len = this.length(); + return this.write(writer, 0, 0); + } + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indention of the top level. + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + int length = this.length(); writer.write('['); - for (int i = 0; i < len; i += 1) { - if (b) { - writer.write(','); + if (length == 1) { + JSONObject.writeValue(writer, this.myArrayList.get(0), + indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + JSONObject.writeValue(writer, this.myArrayList.get(i), + indentFactor, newindent); + commanate = true; } - Object v = this.myArrayList.get(i); - if (v instanceof JSONObject) { - ((JSONObject)v).write(writer); - } else if (v instanceof JSONArray) { - ((JSONArray)v).write(writer); - } else { - writer.write(JSONObject.valueToString(v)); + if (indentFactor > 0) { + writer.write('\n'); } - b = true; + JSONObject.indent(writer, indent); } writer.write(']'); return writer; @@ -917,4 +903,4 @@ public class JSONArray { throw new JSONException(e); } } -} \ No newline at end of file +} diff --git a/JSONObject.java b/JSONObject.java index f8ee359..5b05255 100755 --- a/JSONObject.java +++ b/JSONObject.java @@ -25,10 +25,11 @@ SOFTWARE. */ import java.io.IOException; +import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; @@ -38,54 +39,58 @@ import java.util.Map; import java.util.ResourceBundle; /** - * A JSONObject is an unordered collection of name/value pairs. Its - * external form is a string wrapped in curly braces with colons between the - * names and values, and commas between the values and names. The internal form - * is an object having get and opt methods for - * accessing the values by name, and put methods for adding or - * replacing values by name. The values can be any of these types: - * Boolean, JSONArray, JSONObject, - * Number, String, or the JSONObject.NULL - * object. A JSONObject constructor can be used to convert an external form - * JSON text into an internal form whose values can be retrieved with the - * get and opt methods, or to convert values into a - * JSON text using the put and toString methods. - * A get method returns a value if one can be found, and throws an - * exception if one cannot be found. An opt method returns a - * default value instead of throwing an exception, and so is useful for - * obtaining optional values. + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having get and opt methods for accessing the + * values by name, and put methods for adding or replacing values + * by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, + * String, or the JSONObject.NULL object. A JSONObject + * constructor can be used to convert an external form JSON text into an + * internal form whose values can be retrieved with the get and + * opt methods, or to convert values into a JSON text using the + * put and toString methods. A get method + * returns a value if one can be found, and throws an exception if one cannot be + * found. An opt method returns a default value instead of throwing + * an exception, and so is useful for obtaining optional values. *

* The generic get() and opt() methods return an * object, which you can cast or query for type. There are also typed * get and opt methods that do type checking and type - * coercion for you. The opt methods differ from the get methods in that they - * do not throw. Instead, they return a specified value, such as null. + * coercion for you. The opt methods differ from the get methods in that they do + * not throw. Instead, they return a specified value, such as null. *

* The put methods add or replace values in an object. For example, - *

myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * + *
+ * myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * 
+ * * produces the string {"JSON": "Hello, World"}. *

* The texts produced by the toString methods strictly conform to - * the JSON syntax rules. - * The constructors are more forgiving in the texts they will accept: + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: *

+ * * @author JSON.org - * @version 2011-11-24 + * @version 2012-04-20 */ public class JSONObject { @@ -1154,60 +1159,72 @@ public class JSONObject { * @return A String correctly formatted for insertion in a JSON text. */ public static String quote(String string) { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + try { + return quote(string, sw).toString(); + } catch (IOException ignored) { + // will never happen - we are writing to a string writer + return ""; + } + } + } + + public static Writer quote(String string, Writer w) throws IOException { if (string == null || string.length() == 0) { - return "\"\""; + w.write("\"\""); + return w; } - char b; - char c = 0; - String hhhh; - int i; - int len = string.length(); - StringBuffer sb = new StringBuffer(len + 4); + char b; + char c = 0; + String hhhh; + int i; + int len = string.length(); - sb.append('"'); + w.write('"'); for (i = 0; i < len; i += 1) { b = c; c = string.charAt(i); switch (c) { case '\\': case '"': - sb.append('\\'); - sb.append(c); + w.write('\\'); + w.write(c); break; case '/': if (b == '<') { - sb.append('\\'); + w.write('\\'); } - sb.append(c); + w.write(c); break; case '\b': - sb.append("\\b"); + w.write("\\b"); break; case '\t': - sb.append("\\t"); + w.write("\\t"); break; case '\n': - sb.append("\\n"); + w.write("\\n"); break; case '\f': - sb.append("\\f"); + w.write("\\f"); break; case '\r': - sb.append("\\r"); + w.write("\\r"); break; default: - if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || - (c >= '\u2000' && c < '\u2100')) { + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { hhhh = "000" + Integer.toHexString(c); - sb.append("\\u" + hhhh.substring(hhhh.length() - 4)); + w.write("\\u" + hhhh.substring(hhhh.length() - 4)); } else { - sb.append(c); + w.write(c); } } } - sb.append('"'); - return sb.toString(); + w.write('"'); + return w; } /** @@ -1328,20 +1345,7 @@ public class JSONObject { */ public String toString() { try { - Iterator keys = this.keys(); - StringBuffer sb = new StringBuffer("{"); - - while (keys.hasNext()) { - if (sb.length() > 1) { - sb.append(','); - } - Object o = keys.next(); - sb.append(quote(o.toString())); - sb.append(':'); - sb.append(valueToString(this.map.get(o))); - } - sb.append('}'); - return sb.toString(); + return this.toString(0); } catch (Exception e) { return null; } @@ -1361,67 +1365,12 @@ public class JSONObject { * @throws JSONException If the object contains an invalid number. */ public String toString(int indentFactor) throws JSONException { - return this.toString(indentFactor, 0); - } - - - /** - * Make a prettyprinted JSON text of this JSONObject. - *

- * Warning: This method assumes that the data structure is acyclical. - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @param indent The indentation of the top level. - * @return a printable, displayable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If the object contains an invalid number. - */ - String toString(int indentFactor, int indent) throws JSONException { - int i; - int length = this.length(); - if (length == 0) { - return "{}"; + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); } - Iterator keys = this.keys(); - int newindent = indent + indentFactor; - Object object; - StringBuffer sb = new StringBuffer("{"); - if (length == 1) { - object = keys.next(); - sb.append(quote(object.toString())); - sb.append(": "); - sb.append(valueToString(this.map.get(object), indentFactor, - indent)); - } else { - while (keys.hasNext()) { - object = keys.next(); - if (sb.length() > 1) { - sb.append(",\n"); - } else { - sb.append('\n'); - } - for (i = 0; i < newindent; i += 1) { - sb.append(' '); - } - sb.append(quote(object.toString())); - sb.append(": "); - sb.append(valueToString(this.map.get(object), indentFactor, - newindent)); - } - if (sb.length() > 1) { - sb.append('\n'); - for (i = 0; i < indent; i += 1) { - sb.append(' '); - } - } - } - sb.append('}'); - return sb.toString(); } - /** * Make a JSON text of an Object value. If the object has an * value.toJSONString() method, then that method will be used to produce @@ -1478,63 +1427,6 @@ public class JSONObject { return quote(value.toString()); } - - /** - * Make a prettyprinted JSON text of an object value. - *

- * Warning: This method assumes that the data structure is acyclical. - * @param value The value to be serialized. - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @param indent The indentation of the top level. - * @return a printable, displayable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If the object contains an invalid number. - */ - static String valueToString( - Object value, - int indentFactor, - int indent - ) throws JSONException { - if (value == null || value.equals(null)) { - return "null"; - } - try { - if (value instanceof JSONString) { - Object o = ((JSONString)value).toJSONString(); - if (o instanceof String) { - return (String)o; - } - } - } catch (Exception ignore) { - } - if (value instanceof Number) { - return numberToString((Number) value); - } - if (value instanceof Boolean) { - return value.toString(); - } - if (value instanceof JSONObject) { - return ((JSONObject)value).toString(indentFactor, indent); - } - if (value instanceof JSONArray) { - return ((JSONArray)value).toString(indentFactor, indent); - } - if (value instanceof Map) { - return new JSONObject((Map)value).toString(indentFactor, indent); - } - if (value instanceof Collection) { - return new JSONArray((Collection)value).toString(indentFactor, indent); - } - if (value.getClass().isArray()) { - return new JSONArray(value).toString(indentFactor, indent); - } - return quote(value.toString()); - } - - /** * Wrap an object, if necessary. If the object is null, return the NULL * object. If it is an array or collection, wrap it in a JSONArray. If @@ -1599,27 +1491,98 @@ public class JSONObject { * @throws JSONException */ public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + + static final Writer writeValue(Writer writer, Object value, + int indentFactor, int indent) throws JSONException, IOException { + if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + new JSONObject((Map) value).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + new JSONArray((Collection) value).write(writer, indentFactor, + indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writer.write(numberToString((Number) value)); + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else if (value == null || value.equals(null)) { + writer.write("null"); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { try { - boolean commanate = false; + boolean commanate = false; + final int length = this.length(); Iterator keys = this.keys(); writer.write('{'); - while (keys.hasNext()) { - if (commanate) { - writer.write(','); - } + if (length == 1) { Object key = keys.next(); writer.write(quote(key.toString())); writer.write(':'); - Object value = this.map.get(key); - if (value instanceof JSONObject) { - ((JSONObject)value).write(writer); - } else if (value instanceof JSONArray) { - ((JSONArray)value).write(writer); - } else { - writer.write(valueToString(value)); + if (indentFactor > 0) { + writer.write(' '); } - commanate = true; + writeValue(writer, this.map.get(key), indentFactor, indent); + } else if (length != 0) { + final int newindent = indent + indentFactor; + while (keys.hasNext()) { + Object key = keys.next(); + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, + newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); } writer.write('}'); return writer; @@ -1627,4 +1590,4 @@ public class JSONObject { throw new JSONException(exception); } } -} \ No newline at end of file +}