diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index c6c867b..1e2c32c 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -5,7 +5,6 @@ Public Domain. */ import java.io.IOException; -import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Array; import java.math.BigDecimal; @@ -1730,7 +1729,10 @@ public class JSONArray implements Iterable { */ @SuppressWarnings("resource") public String toString(int indentFactor) throws JSONException { - StringWriter sw = new StringWriter(); + // each value requires a comma, so multiply the count by 2 + // We don't want to oversize the initial capacity + int initialSize = myArrayList.size() * 2; + Writer sw = new StringBuilderWriter(Math.max(initialSize, 16)); return this.write(sw, indentFactor, 0).toString(); } diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 8d90ed1..27bc794 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -6,7 +6,6 @@ Public Domain. import java.io.Closeable; import java.io.IOException; -import java.io.StringWriter; import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -2266,7 +2265,10 @@ public class JSONObject { */ @SuppressWarnings("resource") public static String quote(String string) { - StringWriter sw = new StringWriter(); + if (string == null || string.isEmpty()) { + return "\"\""; + } + Writer sw = new StringBuilderWriter(string.length() + 2); try { return quote(string, sw).toString(); } catch (IOException ignored) { @@ -2665,7 +2667,10 @@ public class JSONObject { */ @SuppressWarnings("resource") public String toString(int indentFactor) throws JSONException { - StringWriter w = new StringWriter(); + // 6 characters are the minimum to serialise a key value pair e.g.: "k":1, + // and we don't want to oversize the initial capacity + int initialSize = map.size() * 6; + Writer w = new StringBuilderWriter(Math.max(initialSize, 16)); return this.write(w, indentFactor, 0).toString(); } @@ -2808,6 +2813,7 @@ public class JSONObject { if (value == null || value.equals(null)) { writer.write("null"); } else if (value instanceof JSONString) { + // JSONString must be checked first, so it can overwrite behaviour of other types below Object o; try { o = ((JSONString) value).toJSONString(); @@ -2815,6 +2821,10 @@ public class JSONObject { throw new JSONException(e); } writer.write(o != null ? o.toString() : quote(value.toString())); + } else if (value instanceof String) { + // assuming most values are Strings, so testing it early + quote(value.toString(), writer); + return writer; } else if (value instanceof Number) { // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary final String numberAsString = numberToString((Number) value); diff --git a/src/main/java/org/json/StringBuilderWriter.java b/src/main/java/org/json/StringBuilderWriter.java new file mode 100644 index 0000000..4aaa490 --- /dev/null +++ b/src/main/java/org/json/StringBuilderWriter.java @@ -0,0 +1,92 @@ +package org.json; + +import java.io.IOException; +import java.io.Writer; + +/** + * Performance optimised alternative for {@link java.io.StringWriter} + * using internally a {@link StringBuilder} instead of a {@link StringBuffer}. + */ +public class StringBuilderWriter extends Writer { + private final StringBuilder builder; + + /** + * Create a new string builder writer using the default initial string-builder buffer size. + */ + public StringBuilderWriter() { + builder = new StringBuilder(); + lock = builder; + } + + /** + * Create a new string builder writer using the specified initial string-builder buffer size. + * + * @param initialSize The number of {@code char} values that will fit into this buffer + * before it is automatically expanded + * + * @throws IllegalArgumentException If {@code initialSize} is negative + */ + public StringBuilderWriter(int initialSize) { + builder = new StringBuilder(initialSize); + lock = builder; + } + + @Override + public void write(int c) { + builder.append((char) c); + } + + @Override + public void write(char[] cbuf, int offset, int length) { + if ((offset < 0) || (offset > cbuf.length) || (length < 0) || + ((offset + length) > cbuf.length) || ((offset + length) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (length == 0) { + return; + } + builder.append(cbuf, offset, length); + } + + @Override + public void write(String str) { + builder.append(str); + } + + @Override + public void write(String str, int offset, int length) { + builder.append(str, offset, offset + length); + } + + @Override + public StringBuilderWriter append(CharSequence csq) { + write(String.valueOf(csq)); + return this; + } + + @Override + public StringBuilderWriter append(CharSequence csq, int start, int end) { + if (csq == null) { + csq = "null"; + } + return append(csq.subSequence(start, end)); + } + + @Override + public StringBuilderWriter append(char c) { + write(c); + return this; + } + + @Override + public String toString() { + return builder.toString(); + } + + @Override + public void flush() { + } + + @Override + public void close() throws IOException { + } +} diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java index b4fee3e..235df18 100644 --- a/src/test/java/org/json/junit/JSONStringTest.java +++ b/src/test/java/org/json/junit/JSONStringTest.java @@ -319,6 +319,22 @@ public class JSONStringTest { } } + @Test + public void testEnumJSONString() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("key", MyEnum.MY_ENUM); + assertEquals("{\"key\":\"myJsonString\"}", jsonObject.toString()); + } + + private enum MyEnum implements JSONString { + MY_ENUM; + + @Override + public String toJSONString() { + return "\"myJsonString\""; + } + } + /** * A JSONString that returns a valid JSON string value. */ diff --git a/src/test/java/org/json/junit/StringBuilderWriterTest.java b/src/test/java/org/json/junit/StringBuilderWriterTest.java new file mode 100644 index 0000000..b12f5db --- /dev/null +++ b/src/test/java/org/json/junit/StringBuilderWriterTest.java @@ -0,0 +1,60 @@ +package org.json.junit; + +import static org.junit.Assert.assertEquals; + +import org.json.StringBuilderWriter; +import org.junit.Before; +import org.junit.Test; + +public class StringBuilderWriterTest { + private StringBuilderWriter writer; + + @Before + public void setUp() { + writer = new StringBuilderWriter(); + } + + @Test + public void testWriteChar() { + writer.write('a'); + assertEquals("a", writer.toString()); + } + + @Test + public void testWriteCharArray() { + char[] chars = {'a', 'b', 'c'}; + writer.write(chars, 0, 3); + assertEquals("abc", writer.toString()); + } + + @Test + public void testWriteString() { + writer.write("hello"); + assertEquals("hello", writer.toString()); + } + + @Test + public void testWriteStringWithOffsetAndLength() { + writer.write("hello world", 6, 5); + assertEquals("world", writer.toString()); + } + + @Test + public void testAppendCharSequence() { + writer.append("hello"); + assertEquals("hello", writer.toString()); + } + + @Test + public void testAppendCharSequenceWithStartAndEnd() { + CharSequence csq = "hello world"; + writer.append(csq, 6, 11); + assertEquals("world", writer.toString()); + } + + @Test + public void testAppendChar() { + writer.append('a'); + assertEquals("a", writer.toString()); + } +} \ No newline at end of file