1
0
Fork 0
mirror of https://github.com/ethauvin/JSON-java.git synced 2025-06-17 07:50:52 -07:00

Performance improvments by Valentin Valchev

This commit is contained in:
Douglas Crockford 2012-04-22 16:52:45 -07:00
parent 319e3b9d84
commit 9115ada84d
2 changed files with 224 additions and 275 deletions

View file

@ -25,6 +25,7 @@ SOFTWARE.
*/ */
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
@ -60,23 +61,23 @@ import java.util.Map;
* accept: * accept:
* <ul> * <ul>
* <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
* before the closing bracket.</li> * before the closing bracket.</li>
* <li>The <code>null</code> value will be inserted when there * <li>The <code>null</code> value will be inserted when there is <code>,</code>
* is <code>,</code>&nbsp;<small>(comma)</small> elision.</li> * &nbsp;<small>(comma)</small> elision.</li>
* <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
* quote)</small>.</li> * quote)</small>.</li>
* <li>Strings do not need to be quoted at all if they do not begin with a quote * <li>Strings do not need to be quoted at all if they do not begin with a quote
* or single quote, and if they do not contain leading or trailing spaces, * or single quote, and if they do not contain leading or trailing spaces, and
* and if they do not contain any of these characters: * if they do not contain any of these characters:
* <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and
* and if they are not the reserved words <code>true</code>, * if they are not the reserved words <code>true</code>, <code>false</code>, or
* <code>false</code>, or <code>null</code>.</li> * <code>null</code>.</li>
* <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as * <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as
* well as by <code>,</code> <small>(comma)</small>.</li> * well as by <code>,</code> <small>(comma)</small>.</li>
* </ul> * </ul>
*
* @author JSON.org * @author JSON.org
* @version 2011-12-19 * @version 2012-04-20
*/ */
public class JSONArray { public class JSONArray {
@ -555,8 +556,8 @@ public class JSONArray {
public String optString(int index, String defaultValue) { public String optString(int index, String defaultValue) {
Object object = this.opt(index); Object object = this.opt(index);
return JSONObject.NULL.equals(object) return JSONObject.NULL.equals(object)
? defaultValue ? defaultValue : object
: object.toString(); .toString();
} }
@ -834,56 +835,15 @@ public class JSONArray {
* @throws JSONException * @throws JSONException
*/ */
public String toString(int indentFactor) 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. * Write the contents of the JSONArray as JSON text to a writer. For
* Warning: This method assumes that the data structure is acyclical. * compactness, no whitespace is added.
* @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.
* <p> * <p>
* Warning: This method assumes that the data structure is acyclical. * Warning: This method assumes that the data structure is acyclical.
* *
@ -891,25 +851,51 @@ public class JSONArray {
* @throws JSONException * @throws JSONException
*/ */
public Writer write(Writer writer) throws JSONException { public Writer write(Writer writer) throws JSONException {
try { return this.write(writer, 0, 0);
boolean b = false; }
int len = this.length();
/**
* Write the contents of the JSONArray as JSON text to a writer. For
* compactness, no whitespace is added.
* <p>
* 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('['); writer.write('[');
for (int i = 0; i < len; i += 1) { if (length == 1) {
if (b) { JSONObject.writeValue(writer, this.myArrayList.get(0),
writer.write(','); 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 (indentFactor > 0) {
if (v instanceof JSONObject) { writer.write('\n');
((JSONObject)v).write(writer);
} else if (v instanceof JSONArray) {
((JSONArray)v).write(writer);
} else {
writer.write(JSONObject.valueToString(v));
} }
b = true; JSONObject.indent(writer, indent);
} }
writer.write(']'); writer.write(']');
return writer; return writer;
@ -917,4 +903,4 @@ public class JSONArray {
throw new JSONException(e); throw new JSONException(e);
} }
} }
} }

View file

@ -25,10 +25,11 @@ SOFTWARE.
*/ */
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
@ -38,54 +39,58 @@ import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
/** /**
* A JSONObject is an unordered collection of name/value pairs. Its * A JSONObject is an unordered collection of name/value pairs. Its external
* external form is a string wrapped in curly braces with colons between the * form is a string wrapped in curly braces with colons between the names and
* names and values, and commas between the values and names. The internal form * values, and commas between the values and names. The internal form is an
* is an object having <code>get</code> and <code>opt</code> methods for * object having <code>get</code> and <code>opt</code> methods for accessing the
* accessing the values by name, and <code>put</code> methods for adding or * values by name, and <code>put</code> methods for adding or replacing values
* replacing values by name. The values can be any of these types: * by name. The values can be any of these types: <code>Boolean</code>,
* <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
* <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code> * <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject
* object. A JSONObject constructor can be used to convert an external form * constructor can be used to convert an external form JSON text into an
* JSON text into an internal form whose values can be retrieved with the * internal form whose values can be retrieved with the <code>get</code> and
* <code>get</code> and <code>opt</code> methods, or to convert values into a * <code>opt</code> methods, or to convert values into a JSON text using the
* JSON text using the <code>put</code> and <code>toString</code> methods. * <code>put</code> and <code>toString</code> methods. A <code>get</code> method
* A <code>get</code> method returns a value if one can be found, and throws an * returns a value if one can be found, and throws an exception if one cannot be
* exception if one cannot be found. An <code>opt</code> method returns a * found. An <code>opt</code> method returns a default value instead of throwing
* default value instead of throwing an exception, and so is useful for * an exception, and so is useful for obtaining optional values.
* obtaining optional values.
* <p> * <p>
* The generic <code>get()</code> and <code>opt()</code> methods return an * The generic <code>get()</code> and <code>opt()</code> methods return an
* object, which you can cast or query for type. There are also typed * object, which you can cast or query for type. There are also typed
* <code>get</code> and <code>opt</code> methods that do type checking and type * <code>get</code> and <code>opt</code> methods that do type checking and type
* coercion for you. The opt methods differ from the get methods in that they * coercion for you. The opt methods differ from the get methods in that they do
* do not throw. Instead, they return a specified value, such as null. * not throw. Instead, they return a specified value, such as null.
* <p> * <p>
* The <code>put</code> methods add or replace values in an object. For example, * The <code>put</code> methods add or replace values in an object. For example,
* <pre>myString = new JSONObject().put("JSON", "Hello, World!").toString();</pre> *
* <pre>
* myString = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
* </pre>
*
* produces the string <code>{"JSON": "Hello, World"}</code>. * produces the string <code>{"JSON": "Hello, World"}</code>.
* <p> * <p>
* The texts produced by the <code>toString</code> methods strictly conform to * The texts produced by the <code>toString</code> methods strictly conform to
* the JSON syntax rules. * the JSON syntax rules. The constructors are more forgiving in the texts they
* The constructors are more forgiving in the texts they will accept: * will accept:
* <ul> * <ul>
* <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
* before the closing brace.</li> * before the closing brace.</li>
* <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
* quote)</small>.</li> * quote)</small>.</li>
* <li>Strings do not need to be quoted at all if they do not begin with a quote * <li>Strings do not need to be quoted at all if they do not begin with a quote
* or single quote, and if they do not contain leading or trailing spaces, * or single quote, and if they do not contain leading or trailing spaces, and
* and if they do not contain any of these characters: * if they do not contain any of these characters:
* <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers * <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and
* and if they are not the reserved words <code>true</code>, * if they are not the reserved words <code>true</code>, <code>false</code>, or
* <code>false</code>, or <code>null</code>.</li> * <code>null</code>.</li>
* <li>Keys can be followed by <code>=</code> or <code>=></code> as well as * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by
* by <code>:</code>.</li> * <code>:</code>.</li>
* <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as
* well as by <code>,</code> <small>(comma)</small>.</li> * well as by <code>,</code> <small>(comma)</small>.</li>
* </ul> * </ul>
*
* @author JSON.org * @author JSON.org
* @version 2011-11-24 * @version 2012-04-20
*/ */
public class JSONObject { public class JSONObject {
@ -1154,60 +1159,72 @@ public class JSONObject {
* @return A String correctly formatted for insertion in a JSON text. * @return A String correctly formatted for insertion in a JSON text.
*/ */
public static String quote(String string) { 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) { if (string == null || string.length() == 0) {
return "\"\""; w.write("\"\"");
return w;
} }
char b; char b;
char c = 0; char c = 0;
String hhhh; String hhhh;
int i; int i;
int len = string.length(); int len = string.length();
StringBuffer sb = new StringBuffer(len + 4);
sb.append('"'); w.write('"');
for (i = 0; i < len; i += 1) { for (i = 0; i < len; i += 1) {
b = c; b = c;
c = string.charAt(i); c = string.charAt(i);
switch (c) { switch (c) {
case '\\': case '\\':
case '"': case '"':
sb.append('\\'); w.write('\\');
sb.append(c); w.write(c);
break; break;
case '/': case '/':
if (b == '<') { if (b == '<') {
sb.append('\\'); w.write('\\');
} }
sb.append(c); w.write(c);
break; break;
case '\b': case '\b':
sb.append("\\b"); w.write("\\b");
break; break;
case '\t': case '\t':
sb.append("\\t"); w.write("\\t");
break; break;
case '\n': case '\n':
sb.append("\\n"); w.write("\\n");
break; break;
case '\f': case '\f':
sb.append("\\f"); w.write("\\f");
break; break;
case '\r': case '\r':
sb.append("\\r"); w.write("\\r");
break; break;
default: default:
if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
(c >= '\u2000' && c < '\u2100')) { || (c >= '\u2000' && c < '\u2100')) {
hhhh = "000" + Integer.toHexString(c); hhhh = "000" + Integer.toHexString(c);
sb.append("\\u" + hhhh.substring(hhhh.length() - 4)); w.write("\\u" + hhhh.substring(hhhh.length() - 4));
} else { } else {
sb.append(c); w.write(c);
} }
} }
} }
sb.append('"'); w.write('"');
return sb.toString(); return w;
} }
/** /**
@ -1328,20 +1345,7 @@ public class JSONObject {
*/ */
public String toString() { public String toString() {
try { try {
Iterator keys = this.keys(); return this.toString(0);
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();
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
@ -1361,67 +1365,12 @@ public class JSONObject {
* @throws JSONException If the object contains an invalid number. * @throws JSONException If the object contains an invalid number.
*/ */
public String toString(int indentFactor) throws JSONException { public String toString(int indentFactor) throws JSONException {
return this.toString(indentFactor, 0); StringWriter w = new StringWriter();
} synchronized (w.getBuffer()) {
return this.write(w, indentFactor, 0).toString();
/**
* Make a prettyprinted JSON text of this JSONObject.
* <p>
* 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 <code>{</code>&nbsp;<small>(left brace)</small> and ending
* with <code>}</code>&nbsp;<small>(right brace)</small>.
* @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 "{}";
} }
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 * Make a JSON text of an Object value. If the object has an
* value.toJSONString() method, then that method will be used to produce * value.toJSONString() method, then that method will be used to produce
@ -1478,63 +1427,6 @@ public class JSONObject {
return quote(value.toString()); return quote(value.toString());
} }
/**
* Make a prettyprinted JSON text of an object value.
* <p>
* 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 <code>{</code>&nbsp;<small>(left brace)</small> and ending
* with <code>}</code>&nbsp;<small>(right brace)</small>.
* @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 * 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 * object. If it is an array or collection, wrap it in a JSONArray. If
@ -1599,27 +1491,98 @@ public class JSONObject {
* @throws JSONException * @throws JSONException
*/ */
public Writer write(Writer writer) 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.
* <p>
* 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 { try {
boolean commanate = false; boolean commanate = false;
final int length = this.length();
Iterator keys = this.keys(); Iterator keys = this.keys();
writer.write('{'); writer.write('{');
while (keys.hasNext()) { if (length == 1) {
if (commanate) {
writer.write(',');
}
Object key = keys.next(); Object key = keys.next();
writer.write(quote(key.toString())); writer.write(quote(key.toString()));
writer.write(':'); writer.write(':');
Object value = this.map.get(key); if (indentFactor > 0) {
if (value instanceof JSONObject) { writer.write(' ');
((JSONObject)value).write(writer);
} else if (value instanceof JSONArray) {
((JSONArray)value).write(writer);
} else {
writer.write(valueToString(value));
} }
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('}'); writer.write('}');
return writer; return writer;
@ -1627,4 +1590,4 @@ public class JSONObject {
throw new JSONException(exception); throw new JSONException(exception);
} }
} }
} }